feat(v5): Disc-Typ aus dem Dateisystem — INDX0300 ist UHD, VIDEO_TS ist DVD

WAS: laufwerke.typVerfeinern liest je Disc einmal BDMV/index.bdmv (Kopf
„INDX" + Fassung: 0300 = UHD-Format, 0100/0200 = Blu-ray) bzw. sieht
VIDEO_TS (DVD) und überschreibt damit, was Steuercode und Größe sagten;
das Ergebnis wird je Laufwerk und Disc-Größe gemerkt (die Wache fragt
alle 3 s). Gemessen am Akira-UHD-Abbild unter F:\Video\backup\UHD_AKIRA
(12.09.2026): INDX0300.
WARUM: Durchsicht 12.09.2026, Fund F10 — bei manchen USB-Brücken
antwortet IOCTL_CDROM_DISK_TYPE nicht („unbekannt" → ISO), und die Größe
allein machte aus einer 4K-Disc mit 48 GB Daten eine „bluray" (falsches
Preset). Am echten Laufwerk mit einer gewöhnlichen Blu-ray noch zu
messen, dass dort 0200 steht.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-09-12 21:49:49 +02:00
co-authored by Claude Opus 5
parent 0fc87aa022
commit fc2d16ca38
3 changed files with 114 additions and 2 deletions
+30 -2
View File
@@ -20,6 +20,7 @@ import {
listOptischeLaufwerke,
} from '../laufwerk/disc'
import { beurteilen, ereignisseLesen } from '../laufwerk/ereignisse'
import { typAusDateisystem } from '../metadaten/discmerkmale'
import { DiscWache } from '../laufwerk/wache'
import type { Spur } from '../rip/audiocd'
@@ -40,6 +41,9 @@ export class LaufwerkDienst {
* Leer im normalen Betrieb; die Win32-Schicht bleibt dann der einzige Weg. */
readonly fake: string
readonly bereit: Promise<void>
/** 5.7.0 (F10): Der Typ aus dem Dateisystem, je Laufwerk und Disc-Größe
* gemerkt — die Wache fragt alle 3 s, die Disc ändert sich nicht. */
private typGemerkt = new Map<string, { groesse: number; typ: GeraeteInfo['typ'] }>()
constructor(
private readonly kontext: KernKontext,
@@ -99,7 +103,7 @@ export class LaufwerkDienst {
const { win32Api } = await import('../laufwerk/win32')
this.api = win32Api()
this.wache = new DiscWache(
() => listOptischeLaufwerke(this.api!).map((g) => geraeteInfo(this.api!, g)),
() => listOptischeLaufwerke(this.api!).map((g) => this.typVerfeinern(geraeteInfo(this.api!, g))),
(m) => this.wacheMeldung(m),
wacheFehler,
)
@@ -115,6 +119,30 @@ export class LaufwerkDienst {
return this.wache?.aktuellerStand() ?? []
}
/** Was auf der Disc liegt, schlägt Steuercode und Größe (F10): BDMV mit
* INDX0300 ist UHD, sonst Blu-ray; VIDEO_TS ist DVD. Nur bei „ready",
* und nur einmal je Disc (gemerkt an der Größe). */
private typVerfeinern(geraet: GeraeteInfo): GeraeteInfo {
if (geraet.status !== 'ready' || geraet.typ === 'cd') {
this.typGemerkt.delete(geraet.id)
return geraet
}
const alt = this.typGemerkt.get(geraet.id)
if (alt !== undefined && alt.groesse === geraet.groesseBytes) return alt.typ === geraet.typ ? geraet : { ...geraet, typ: alt.typ }
let typ = geraet.typ
try {
const gesehen = typAusDateisystem(geraet.pfad)
if (gesehen !== null && gesehen !== geraet.typ) {
this.kontext.protokoll.schreiben(`Laufwerk ${geraet.id}: Typ laut Dateisystem ${gesehen} (Steuercode/Größe sagten ${geraet.typ})`)
typ = gesehen
}
} catch (fehler) {
this.kontext.protokoll.schreiben(`Laufwerk ${geraet.id}: Dateisystem nicht lesbar (${String(fehler)})`)
}
this.typGemerkt.set(geraet.id, { groesse: geraet.groesseBytes, typ })
return typ === geraet.typ ? geraet : { ...geraet, typ }
}
/** Der Geräte-Name zu einer Kennung — oder null. */
geraetVon(id: string): string | null {
if (this.api === null) return null
@@ -126,7 +154,7 @@ export class LaufwerkDienst {
if (this.fake.length > 0) return id === this.fake ? this.fakeGeraet() : null
const geraet = this.geraetVon(id)
if (geraet === null || this.api === null) return null
return geraeteInfo(this.api, geraet)
return this.typVerfeinern(geraeteInfo(this.api, geraet))
}
audioToc(id: string): Spur[] {
@@ -250,6 +250,46 @@ export function discZusatzAbtrennen(titel: string): DiscZusatz {
return { titel: rest.replace(new RegExp(`${TRENNER}+$`), '').trim(), discNr, staffel }
}
/** Der Disc-Typ aus dem, was auf der Disc liegt (5.7.0, Durchsicht F10) —
* unabhängig vom Windows-Steuercode, der bei manchen USB-Brücken nicht
* antwortet, und genauer als die Größe (eine 4K-Disc mit 48 GB Daten
* wurde vorher „bluray"):
* * BDMV/index.bdmv beginnt mit „INDX" + Fassung: „0300" ist das
* UHD-Format (BDMV 3.0), „0100"/„0200" eine gewöhnliche Blu-ray —
* am Akira-UHD-Abbild (F:\Video\backup\UHD_AKIRA) am 12.09.2026 gemessen:
* INDX0300.
* * VIDEO_TS → DVD.
* null, wenn nichts davon zu sehen ist (Audio-CD, Datendisc, Lesefehler). */
export function typAusIndexKopf(kopf: Buffer): 'uhd' | 'bluray' | null {
if (kopf.length < 8 || kopf.subarray(0, 4).toString('latin1') !== 'INDX') return null
const fassung = kopf.subarray(4, 8).toString('latin1')
if (fassung === '0300') return 'uhd'
if (fassung === '0100' || fassung === '0200') return 'bluray'
return null
}
export function typAusDateisystem(geraetepfad: string): 'uhd' | 'bluray' | 'dvd' | null {
const wurzel = discWurzel(geraetepfad)
if (wurzel.length === 0) return null
const index = join(wurzel, 'BDMV', 'index.bdmv')
if (existsSync(index)) {
let fd: number | null = null
try {
fd = openSync(index, 'r')
const kopf = lesenBei(fd, 0, 8)
const typ = typAusIndexKopf(kopf)
if (typ !== null) return typ
} catch {
// nicht lesbar — dann entscheidet die Größe (unten)
} finally {
if (fd !== null) closeSync(fd)
}
return 'bluray'
}
if (existsSync(join(wurzel, 'VIDEO_TS'))) return 'dvd'
return null
}
/** \\.\G: → G:\ — unter Windows hängt Windows die Disc selbst ein. */
export function discWurzel(geraetepfad: string): string {
const buchstabe = geraetepfad.replace(/:$/, '').split('\\').pop()!.replace(/:$/, '')