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:
co-authored by
Claude Opus 5
parent
0fc87aa022
commit
fc2d16ca38
@@ -20,6 +20,7 @@ import {
|
|||||||
listOptischeLaufwerke,
|
listOptischeLaufwerke,
|
||||||
} from '../laufwerk/disc'
|
} from '../laufwerk/disc'
|
||||||
import { beurteilen, ereignisseLesen } from '../laufwerk/ereignisse'
|
import { beurteilen, ereignisseLesen } from '../laufwerk/ereignisse'
|
||||||
|
import { typAusDateisystem } from '../metadaten/discmerkmale'
|
||||||
import { DiscWache } from '../laufwerk/wache'
|
import { DiscWache } from '../laufwerk/wache'
|
||||||
import type { Spur } from '../rip/audiocd'
|
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. */
|
* Leer im normalen Betrieb; die Win32-Schicht bleibt dann der einzige Weg. */
|
||||||
readonly fake: string
|
readonly fake: string
|
||||||
readonly bereit: Promise<void>
|
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(
|
constructor(
|
||||||
private readonly kontext: KernKontext,
|
private readonly kontext: KernKontext,
|
||||||
@@ -99,7 +103,7 @@ export class LaufwerkDienst {
|
|||||||
const { win32Api } = await import('../laufwerk/win32')
|
const { win32Api } = await import('../laufwerk/win32')
|
||||||
this.api = win32Api()
|
this.api = win32Api()
|
||||||
this.wache = new DiscWache(
|
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),
|
(m) => this.wacheMeldung(m),
|
||||||
wacheFehler,
|
wacheFehler,
|
||||||
)
|
)
|
||||||
@@ -115,6 +119,30 @@ export class LaufwerkDienst {
|
|||||||
return this.wache?.aktuellerStand() ?? []
|
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. */
|
/** Der Geräte-Name zu einer Kennung — oder null. */
|
||||||
geraetVon(id: string): string | null {
|
geraetVon(id: string): string | null {
|
||||||
if (this.api === null) return 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
|
if (this.fake.length > 0) return id === this.fake ? this.fakeGeraet() : null
|
||||||
const geraet = this.geraetVon(id)
|
const geraet = this.geraetVon(id)
|
||||||
if (geraet === null || this.api === null) return null
|
if (geraet === null || this.api === null) return null
|
||||||
return geraeteInfo(this.api, geraet)
|
return this.typVerfeinern(geraeteInfo(this.api, geraet))
|
||||||
}
|
}
|
||||||
|
|
||||||
audioToc(id: string): Spur[] {
|
audioToc(id: string): Spur[] {
|
||||||
|
|||||||
@@ -250,6 +250,46 @@ export function discZusatzAbtrennen(titel: string): DiscZusatz {
|
|||||||
return { titel: rest.replace(new RegExp(`${TRENNER}+$`), '').trim(), discNr, staffel }
|
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. */
|
/** \\.\G: → G:\ — unter Windows hängt Windows die Disc selbst ein. */
|
||||||
export function discWurzel(geraetepfad: string): string {
|
export function discWurzel(geraetepfad: string): string {
|
||||||
const buchstabe = geraetepfad.replace(/:$/, '').split('\\').pop()!.replace(/:$/, '')
|
const buchstabe = geraetepfad.replace(/:$/, '').split('\\').pop()!.replace(/:$/, '')
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
// 5.7.0 (Durchsicht F10): Der Disc-Typ aus dem Dateisystem — BDMV/index.bdmv
|
||||||
|
// mit „INDX0300" ist UHD (am Akira-UHD-Abbild gemessen), „0200"/„0100" eine
|
||||||
|
// Blu-ray, VIDEO_TS eine DVD. Der Windows-Steuercode antwortet bei manchen
|
||||||
|
// USB-Brücken nicht, und die Größe allein machte aus einer 4K-Disc mit
|
||||||
|
// 48 GB Daten „bluray".
|
||||||
|
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
|
||||||
|
import { tmpdir } from 'node:os'
|
||||||
|
import { join } from 'node:path'
|
||||||
|
import { afterEach, describe, expect, it } from 'vitest'
|
||||||
|
import { typAusIndexKopf } from '../src/kern/metadaten/discmerkmale'
|
||||||
|
|
||||||
|
let wegwerf: string[] = []
|
||||||
|
afterEach(() => {
|
||||||
|
for (const o of wegwerf.splice(0)) rmSync(o, { recursive: true, force: true })
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('typAusIndexKopf', () => {
|
||||||
|
it('INDX0300 ist UHD, INDX0200/0100 Blu-ray, alles andere nichts', () => {
|
||||||
|
expect(typAusIndexKopf(Buffer.from('INDX0300\0\0\0N', 'latin1'))).toBe('uhd')
|
||||||
|
expect(typAusIndexKopf(Buffer.from('INDX0200xxxx', 'latin1'))).toBe('bluray')
|
||||||
|
expect(typAusIndexKopf(Buffer.from('INDX0100xxxx', 'latin1'))).toBe('bluray')
|
||||||
|
expect(typAusIndexKopf(Buffer.from('MOBJ0200', 'latin1'))).toBe(null)
|
||||||
|
expect(typAusIndexKopf(Buffer.from('IND', 'latin1'))).toBe(null)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('typAusDateisystem an einem nachgebauten Laufzeit-Ordner', () => {
|
||||||
|
// discWurzel() rechnet aus \\.\G: den Buchstaben — das geht nur unter
|
||||||
|
// Windows an einem echten Laufwerk. Hier wird deshalb die Kopf-Funktion
|
||||||
|
// über eine echte Datei geprüft, so wie typAusDateisystem sie liest.
|
||||||
|
it('liest die ersten acht Bytes einer echten index.bdmv', () => {
|
||||||
|
const wurzel = mkdtempSync(join(tmpdir(), 'rippy-bdmv-'))
|
||||||
|
wegwerf.push(wurzel)
|
||||||
|
mkdirSync(join(wurzel, 'BDMV'), { recursive: true })
|
||||||
|
const index = join(wurzel, 'BDMV', 'index.bdmv')
|
||||||
|
writeFileSync(index, Buffer.concat([Buffer.from('INDX0300', 'latin1'), Buffer.alloc(64)]))
|
||||||
|
const { openSync, readSync, closeSync } = require('node:fs') as typeof import('node:fs')
|
||||||
|
const fd = openSync(index, 'r')
|
||||||
|
const kopf = Buffer.alloc(8)
|
||||||
|
readSync(fd, kopf, 0, 8, 0)
|
||||||
|
closeSync(fd)
|
||||||
|
expect(typAusIndexKopf(kopf)).toBe('uhd')
|
||||||
|
})
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user