WAS: Die Grenze fürs Titel-Lesen kommt aus der Einstellung titelLesenMinuten (5–60, Vorgabe 15; Einstellungen → Allgemein). Ab der zweiten Minute meldet der Kern jede Minute, wie lange MakeMKV schon liest und wo die Grenze liegt; „Abbrechen" im Dialog beendet den Lauf auch im Kern (titel-abbruch), damit das Laufwerk frei wird. Läuft die Zeit ab, nennt die Meldung Kopierschutz und viele Playlisten als mögliche Gründe statt nur „beschädigte Disc" — mit dem Rat, die Disc im MakeMKV-Programm zu öffnen und die Zeit zu stoppen. WARUM: Durchsicht 12.09.2026, Kandidat 2 (Commander-Entscheid: 15 Minuten plus Regler) — fünf Minuten fest war für ältere DVDs mit Kopierschutz zu kurz, das GUI wartet, Rippy nicht. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
151 lines
5.0 KiB
TypeScript
151 lines
5.0 KiB
TypeScript
// Einstellungen (§ 6.7): Das Fenster liest und schreibt NUR über den Kern;
|
||
// erlaubt sind genau die Schlüssel hier. Dazu die Pfad-Regeln: Ablage
|
||
// (Fertiges — beim Commander der Server) und seit 5.5.0 der Arbeitsordner
|
||
// (Roh-Dateien und Kompression — lokal, schnell; Vorgabe: unter der Ablage,
|
||
// also wie bisher).
|
||
import { join } from 'node:path'
|
||
import { AUFBEWAHRUNG_SCHLUESSEL } from '../ablage/rohaufraeumen'
|
||
import { einstellung, type KernKontext } from '../kontext'
|
||
|
||
export const ERLAUBTE_EINSTELLUNGEN: ReadonlySet<string> = new Set([
|
||
'ablage',
|
||
'arbeitsordner',
|
||
'transcodeEnabled',
|
||
'transcodePresetDvd',
|
||
'transcodePresetBluray',
|
||
'transcodePresetUhd',
|
||
'audioSprachen',
|
||
'untertitelSprachen',
|
||
'mediaServer',
|
||
'mediaServerUrl',
|
||
'mediaServerApiKey',
|
||
'tmdbApiKey',
|
||
'omdbApiKey',
|
||
'makemkvAppKey',
|
||
'updateUrl',
|
||
'setup.done',
|
||
'autoAuswurf',
|
||
'werkzeug.makemkv',
|
||
'werkzeug.handbrake',
|
||
'werkzeug.flac',
|
||
AUFBEWAHRUNG_SCHLUESSEL,
|
||
// 5.5.0
|
||
'automatik',
|
||
'automatikSekunden',
|
||
'benachrichtigungen',
|
||
'discordWebhook',
|
||
'telegramToken',
|
||
'telegramChatId',
|
||
'design',
|
||
// 5.7.0: wie lange makemkvcon die Titel lesen darf (Kandidat 2 der Durchsicht)
|
||
'titelLesenMinuten',
|
||
])
|
||
|
||
/** Die Schlüssel, die Rip und Kompression lesen (Pipeline/Ablage). Das
|
||
* frühere „Allgemeine Preset" (transcodePreset) ist seit 5.5.0 raus —
|
||
* Commander-Wunsch: je Disc-Typ reicht, ein unsichtbarer Übersteuerer
|
||
* verwirrt nur. */
|
||
const PIPELINE_EINSTELLUNGEN = [
|
||
'transcodeEnabled',
|
||
'transcodePresetDvd',
|
||
'transcodePresetBluray',
|
||
'transcodePresetUhd',
|
||
'audioSprachen',
|
||
'untertitelSprachen',
|
||
'mediaServer',
|
||
'mediaServerUrl',
|
||
'mediaServerApiKey',
|
||
] as const
|
||
|
||
export const AUTOMATIK_SEKUNDEN_VORGABE = 30
|
||
/** 5.7.0: Vorgabe 15 min (Commander-Entscheid 12.09.2026) — kopiergeschützte
|
||
* DVDs und Discs mit vielen Playlisten brauchen im MakeMKV-GUI regelmäßig
|
||
* länger als die alten fünf Minuten. */
|
||
export const TITEL_LESEN_MINUTEN_VORGABE = 15
|
||
|
||
export class EinstellungsDienst {
|
||
constructor(private readonly kontext: KernKontext) {}
|
||
|
||
lesen(schluessel: string, vorgabe = ''): string {
|
||
return einstellung(this.kontext, schluessel, vorgabe)
|
||
}
|
||
|
||
/** Wurzel der Ablage — Fertiges, Musik, ISO. */
|
||
ablageOrdner(): string {
|
||
const eingestellt = this.lesen('ablage')
|
||
if (eingestellt.length > 0) return eingestellt
|
||
return join(process.env['USERPROFILE'] ?? '.', 'Videos', 'Rippy')
|
||
}
|
||
|
||
/** Ein eigener Arbeitsordner — '' wenn keiner gesetzt ist. */
|
||
arbeitsordnerEigen(): string {
|
||
return this.lesen('arbeitsordner').trim()
|
||
}
|
||
|
||
/** Wo gearbeitet wird: der eigene Arbeitsordner, sonst die Ablage. */
|
||
arbeitsordner(): string {
|
||
const eigen = this.arbeitsordnerEigen()
|
||
return eigen.length > 0 ? eigen : this.ablageOrdner()
|
||
}
|
||
|
||
/** Wo die Roh-Ordner liegen. */
|
||
rohWurzel(): string {
|
||
return join(this.arbeitsordner(), 'roh')
|
||
}
|
||
|
||
/** Was Pipeline und Ablage brauchen. */
|
||
fuerPipeline(): Record<string, string> {
|
||
const werte: Record<string, string> = {}
|
||
for (const schluessel of PIPELINE_EINSTELLUNGEN) {
|
||
const wert = this.kontext.db?.einstellungLesen(schluessel)
|
||
if (wert !== null && wert !== undefined) werte[schluessel] = wert
|
||
}
|
||
return werte
|
||
}
|
||
|
||
automatikAn(): boolean {
|
||
return this.lesen('automatik', 'an') !== 'aus'
|
||
}
|
||
|
||
/** Grenze fürs Titel-Lesen in Minuten (5–60, Vorgabe 15). */
|
||
titelLesenMinuten(): number {
|
||
const n = Number(this.lesen('titelLesenMinuten', String(TITEL_LESEN_MINUTEN_VORGABE)))
|
||
return Number.isInteger(n) && n >= 5 && n <= 60 ? n : TITEL_LESEN_MINUTEN_VORGABE
|
||
}
|
||
|
||
automatikSekunden(): number {
|
||
const n = Number(this.lesen('automatikSekunden', String(AUTOMATIK_SEKUNDEN_VORGABE)))
|
||
return Number.isInteger(n) && n >= 5 && n <= 600 ? n : AUTOMATIK_SEKUNDEN_VORGABE
|
||
}
|
||
|
||
/** Alle erlaubten Werte ans Fenster — AUCH ans Haupt (Update-Adresse, § 6.8). */
|
||
senden(): void {
|
||
const werte: Record<string, string> = { ablage: this.ablageOrdner() }
|
||
for (const schluessel of ERLAUBTE_EINSTELLUNGEN) {
|
||
const wert = this.kontext.db?.einstellungLesen(schluessel)
|
||
if (wert !== null && wert !== undefined) werte[schluessel] = wert
|
||
}
|
||
this.kontext.anHauptUndFenster({ art: 'einstellungen', werte })
|
||
}
|
||
|
||
/** Schreiben — nur erlaubte Schlüssel, nur mit Datenbank. Liefert, ob es klappte. */
|
||
setzen(schluessel: string, wert: string): boolean {
|
||
if (!ERLAUBTE_EINSTELLUNGEN.has(schluessel)) {
|
||
this.kontext.fehler(`Unbekannter Einstellungs-Schlüssel: ${schluessel}`)
|
||
return false
|
||
}
|
||
if (this.kontext.db === null) {
|
||
this.kontext.fehler('Einstellung nicht gespeichert — die Datenbank ist nicht da.')
|
||
return false
|
||
}
|
||
this.kontext.db.einstellungSchreiben(schluessel, wert)
|
||
this.kontext.protokoll.schreiben(`Einstellung ${schluessel} gesetzt${istGeheim(schluessel) ? '' : `: ${wert.slice(0, 120)}`}`)
|
||
this.senden()
|
||
return true
|
||
}
|
||
}
|
||
|
||
function istGeheim(schluessel: string): boolean {
|
||
return /key|token|webhook|passwort/i.test(schluessel)
|
||
}
|