feat(v5): Etappe W-7 — Audio-CD, ISO-Backup, MakeMKV-Schluesselkette
Ampel / ampel (push) Successful in 1m20s
Ampel / ampel (push) Successful in 1m20s
rip/audiocd.ts: die Rechnungen PUR (MSF→LBA mit den 150 Vorlauf-Frames, TOC-Zerlegung mit Lead-Out-Pflicht und Daten-Track-Bit, 27-Sektoren- Bloecke unter der 64-KB-Treibergrenze, RAW_READ_INFO mit DiskOffset in 2048ern OBWOHL der Sektor 2352 hat, WAV-Kopf, AC/DC-sichere Namen). metadaten/musicbrainz.ts: DiscID nach musicbrainz.org-Rechnung — gegen die ECHTE AC/DC-Kennung getestet (3KVSIWn_…), Vorlauf wird beim Melden wieder HINZUgerechnet; Tags je Spur mit Bindewort-Kuenstlern. laufwerk/disc.ts: audioToc + audioSpurLesen ueber die LaufwerkApi. rip/iso.ts (§ 6.4): 1:1-Abbild fuer unbekannte Discs — async (ein 33-GB-Abbild darf den Kern nicht blockieren), sektor-ausgerichtet, unvollstaendig heisst FEHLER mit Byte-Zahlen, nie Schoenfaerberei. werkzeuge/schluessel.ts (§ 9.1): Dauerlizenz schlaegt Forum-Beta-Key (t=1053, T-Muster aus makemkv_key.py), Ablage in HKCU ueber reg.exe (kein zweiter koffi-Ort), GEFRAGT statt gerechnet (makemkvcon info disc:9999, MSG 5020/5021/5051), abgelehnter Key wird ZURUECKGENOMMEN (schlimmer als keiner: 0 Titel, kein Laufwerk), app_UpdateEnable nur- wenn-fehlt (der 4K-Schluesselkanal). Kern prueft beim Start und taeglich; Dashboard-Kachel zeigt Stand + letzten Pruefzeitpunkt; Dauerlizenz-Feld in den Einstellungen. Pipeline verzweigt nach Disc-Typ: cd → Sektoren→WAV→FLAC mit MusicBrainz-Tags (Ordner 'Kuenstler - Album', ohne Treffer ehrlich 'Track NN'), unknown → ISO. Bibliothek-Eintraege fuer beide. 181 Tests gruen, Smoke gruen. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
c78ff22ab7
commit
2d9cef22b3
@@ -16,7 +16,14 @@ import {
|
||||
} from '../gemeinsam/nachrichten'
|
||||
import { Pipeline } from './ablauf/pipeline'
|
||||
import type { LaufwerkApi } from './laufwerk/codes'
|
||||
import { auswerfenMitGrund, geraeteInfo, kennung, listOptischeLaufwerke } from './laufwerk/disc'
|
||||
import {
|
||||
audioSpurLesen,
|
||||
audioToc,
|
||||
auswerfenMitGrund,
|
||||
geraeteInfo,
|
||||
kennung,
|
||||
listOptischeLaufwerke,
|
||||
} from './laufwerk/disc'
|
||||
import { DiscWache } from './laufwerk/wache'
|
||||
import { discFingerprint, discTitel } from './metadaten/discmerkmale'
|
||||
import { OmdbClient } from './metadaten/omdb'
|
||||
@@ -196,9 +203,25 @@ function einstellungenLaden(): Record<string, string> {
|
||||
return werte
|
||||
}
|
||||
|
||||
function geraetVon(id: string): string | null {
|
||||
if (laufwerkApi === null) return null
|
||||
return listOptischeLaufwerke(laufwerkApi).find((g) => kennung(g) === id) ?? null
|
||||
}
|
||||
|
||||
const ripVerwaltung = new Pipeline({
|
||||
makemkvcon: () => finden('makemkv', db?.einstellungLesen('werkzeug.makemkv') ?? ''),
|
||||
handbrake: () => finden('handbrake', db?.einstellungLesen('werkzeug.handbrake') ?? ''),
|
||||
flac: () => finden('flac', db?.einstellungLesen('werkzeug.flac') ?? ''),
|
||||
audioToc: (id) => {
|
||||
const geraet = geraetVon(id)
|
||||
if (laufwerkApi === null || geraet === null) throw new Error(`Laufwerk ${id}: gibt es nicht`)
|
||||
return audioToc(laufwerkApi, geraet)
|
||||
},
|
||||
audioSpurLesen: (id, start, sektoren, schreiben, fortschritt) => {
|
||||
const geraet = geraetVon(id)
|
||||
if (laufwerkApi === null || geraet === null) throw new Error(`Laufwerk ${id}: gibt es nicht`)
|
||||
return audioSpurLesen(laufwerkApi, geraet, start, sektoren, schreiben, fortschritt)
|
||||
},
|
||||
geraet: (id) => {
|
||||
if (laufwerkApi === null) return null
|
||||
const geraet = listOptischeLaufwerke(laufwerkApi).find((g) => kennung(g) === id)
|
||||
@@ -237,6 +260,7 @@ const ERLAUBTE_EINSTELLUNGEN = new Set([
|
||||
'mediaServerApiKey',
|
||||
'tmdbApiKey',
|
||||
'omdbApiKey',
|
||||
'makemkvAppKey',
|
||||
'autoAuswurf',
|
||||
'werkzeug.makemkv',
|
||||
'werkzeug.handbrake',
|
||||
@@ -252,6 +276,29 @@ function einstellungenSenden(): void {
|
||||
anFenster({ art: 'einstellungen', werte })
|
||||
}
|
||||
|
||||
// ── MakeMKV-Schlüsselkette (§ 9.1): beim Start und täglich ──────────────
|
||||
let schluesselStand: Extract<KernNachricht, { art: 'schluessel-status' }>['stand'] | null = null
|
||||
|
||||
async function schluesselLauf(): Promise<void> {
|
||||
if (process.platform !== 'win32') return
|
||||
try {
|
||||
const { schluesselKette } = await import('./werkzeuge/schluessel')
|
||||
const makemkvcon = finden('makemkv', db?.einstellungLesen('werkzeug.makemkv') ?? '')
|
||||
schluesselStand = await schluesselKette(
|
||||
makemkvcon,
|
||||
db?.einstellungLesen('makemkvAppKey') ?? '',
|
||||
new Date().toISOString(),
|
||||
)
|
||||
anHauptUndFenster({ art: 'schluessel-status', stand: schluesselStand })
|
||||
} catch (fehler) {
|
||||
// R4: Der Schlüssel-Lauf ist Komfort — sein Scheitern wird gesagt.
|
||||
console.error(`[kern] Schlüsselkette: ${String(fehler)}`)
|
||||
anFenster({ art: 'kern-fehler', text: `Schlüsselkette: ${String(fehler)}` })
|
||||
}
|
||||
}
|
||||
// Täglich neu prüfen (§ 9.1) — ohne den Prozess-Exit aufzuhalten.
|
||||
setInterval(() => void schluesselLauf(), 24 * 60 * 60 * 1000).unref()
|
||||
|
||||
async function werkzeugeSenden(): Promise<void> {
|
||||
const makemkvcon = finden('makemkv', db?.einstellungLesen('werkzeug.makemkv') ?? '')
|
||||
const handbrake = finden('handbrake', db?.einstellungLesen('werkzeug.handbrake') ?? '')
|
||||
@@ -336,6 +383,9 @@ process.parentPort.on('message', (ereignis) => {
|
||||
anFenster({ art: 'kern-fehler', text: `Laufwerke: ${laufwerkFehler}` })
|
||||
}
|
||||
anFenster({ art: 'laufwerke', liste: wache?.aktuellerStand() ?? [], ereignisse: [] })
|
||||
if (schluesselStand !== null) {
|
||||
anFenster({ art: 'schluessel-status', stand: schluesselStand })
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -350,3 +400,8 @@ function bereitNachricht(): KernNachricht {
|
||||
}
|
||||
|
||||
process.parentPort.postMessage(bereitNachricht())
|
||||
// Nicht im Smoke: Der Schlüssel-Lauf spricht Forum + makemkvcon an —
|
||||
// der Gerüst-Beweis soll ohne Netz und ohne MakeMKV grün sein.
|
||||
if (!process.argv.includes('--smoke-kern') && process.env['RIPPY_OHNE_SCHLUESSEL'] !== '1') {
|
||||
void schluesselLauf()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user