Native Windows-Auslieferung statt Container
Docker faellt weg. Das Projekt hat keine npm-Abhaengigkeiten und Node fuehrt TypeScript direkt aus - der Container schrieb nur noch drei Programme fest, und dafuer braucht es keine Virtualisierung, sondern einen Ordner. Der Anlass war, dass ADR-0004 zwar `docker compose pull` versprach, es aber nie ein veroeffentlichtes Image gab. Wer installieren wollte, musste whisper.cpp aus dem Quelltext uebersetzen. Plattformschicht (packages/core/src/platform.ts): - Programme werden neben der Anwendung gesucht, nicht im PATH - Daten unter %LOCALAPPDATA%, damit Installation und Update ohne Administratorrechte auskommen - Schriften mitgeliefert: ohne fontsdir setzt libass wortlos eine Ersatzschrift ein - gemessen 720 statt 826 Pixel Zeilenbreite Windows-Eigenheiten, an echtem ffmpeg 8.1 durchprobiert: - Pfade im Filtergraphen brauchen alle drei Schritte (Vorwaertsschraegstriche, escapter Doppelpunkt, Anfuehrungszeichen); je zwei davon reichen nicht - `nice` gibt es nicht, ersetzt durch os.setPriority Auslieferung: - scripts/build-package.ts baut das Paket, laeuft auch unter Linux - install.ps1 ohne Administratorrechte, Autostart ueber den Aufgabenplaner - update legt neue Fassungen bereit; eingespielt wird beim naechsten Start, nie mitten im Stream - ZIP lesen und schreiben ohne Abhaengigkeiten (packages/core/src/zip.ts) Kein `irm ... | iex`-Einzeiler: git.zeasy.dev antwortete instanzweit mit einer Umleitung auf /user/login, und `iex` fuehrte die Anmeldeseite als PowerShell aus. Der Updater verfolgt Umleitungen jetzt nicht und hat keine erfundene Vorgabeadresse mehr. Ausserdem: backup.ps1 war an zwei Stellen dokumentiert, aber nie geschrieben. ADR-0007 ersetzt ADR-0004. 84 Tests, Typpruefung sauber. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AhVSfMFdWnXKCv5JfLnArZ
This commit is contained in:
1 parent
71dd660d01
commit
5f6301268a
41 files changed
+2254
-312
No files matched your search
@@ -2,7 +2,10 @@ import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import type { Word } from './whisper.ts';
|
||||
import { wordsFromJson } from './whisper.ts';
|
||||
import { assColor, assTime, buildAss, buildSrt, isNoise, toLines } from './subtitles.ts';
|
||||
import {
|
||||
assColor, assTime, buildAss, buildSrt, DEFAULT_STYLE, isNoise, lineOptionsFor,
|
||||
fitFontSize, toLines, widthUnits,
|
||||
} from './subtitles.ts';
|
||||
|
||||
function w(text: string, start: number, end: number): Word {
|
||||
return { text, start, end };
|
||||
@@ -89,6 +92,61 @@ test('filtert Geraeuschbeschreibungen', () => {
|
||||
assert.deepEqual(lines.flatMap((l) => l.words.map((x) => x.text)), ['hallo']);
|
||||
});
|
||||
|
||||
// ------------------------------------------------------------ Zeilenbreite
|
||||
|
||||
test('keine Zeile laeuft seitlich aus dem Bild', () => {
|
||||
// Der Fehler, der das ausgeloest hat: die feste Obergrenze von 30 Zeichen
|
||||
// ergibt bei Schriftgroesse 76 rund 1085 px. Nutzbar sind 1080 minus zwei
|
||||
// Seitenraender = 940 px. Gemessen mit libass, nicht gerechnet.
|
||||
const opts = lineOptionsFor(1080);
|
||||
const satz = ('WARUM MACHT DER DAS DENN EINFACH NICHT RICHTIG das ist doch ' +
|
||||
'wirklich nicht so schwer zu verstehen oder etwa doch').split(' ');
|
||||
const lines = toLines(satz.map((t, i) => w(t, i * 0.35, i * 0.35 + 0.3)), opts);
|
||||
|
||||
assert.ok(lines.length > 1);
|
||||
for (const l of lines) {
|
||||
const text = l.words.map((x) => x.text).join(' ');
|
||||
const units = widthUnits(text);
|
||||
assert.ok(units <= opts.maxUnits + 4,
|
||||
`zu breit (${units.toFixed(1)} von ${opts.maxUnits} Normalzeichen): "${text}"`);
|
||||
}
|
||||
});
|
||||
|
||||
test('Versalien zaehlen breiter als Kleinbuchstaben', () => {
|
||||
assert.ok(widthUnits('WARUM') > widthUnits('warum'));
|
||||
assert.equal(widthUnits('warum'), 5);
|
||||
});
|
||||
|
||||
test('Zeilenlaenge haengt an Breite und Schriftgroesse', () => {
|
||||
const schmal = lineOptionsFor(1080, { ...DEFAULT_STYLE, fontSize: 110 });
|
||||
const breit = lineOptionsFor(1080, { ...DEFAULT_STYLE, fontSize: 56 });
|
||||
assert.ok(schmal.maxUnits < breit.maxUnits,
|
||||
'groessere Schrift muss frueher umbrechen');
|
||||
assert.ok(lineOptionsFor(1080).maxUnits < 30, 'die alte 30 war zu viel');
|
||||
});
|
||||
|
||||
test('verkleinert die Schrift fuer Woerter, die nicht umbrechbar sind', () => {
|
||||
// Innerhalb eines Wortes bricht libass nicht um. Deutsche Komposita sind
|
||||
// regelmaessig breiter als das Bild - gemessen 1078 px bei 1080 Bildbreite.
|
||||
const lang = toLines([w('Geschwindigkeitsbegrenzung', 0, 2)]);
|
||||
const kurz = toLines([w('kurz', 0, 2)]);
|
||||
assert.ok(fitFontSize(lang[0]!, 940, DEFAULT_STYLE) < DEFAULT_STYLE.fontSize);
|
||||
assert.equal(fitFontSize(kurz[0]!, 940, DEFAULT_STYLE), DEFAULT_STYLE.fontSize);
|
||||
|
||||
const ass = buildAss(lang, { width: 1080, height: 1920 });
|
||||
assert.match(ass, /\{\\fs\d+\}\{\\k\d+\}Geschwindigkeits/,
|
||||
'die Zeile braucht eine eigene Schriftgroesse');
|
||||
// Kurze Zeilen bleiben unangetastet
|
||||
assert.doesNotMatch(buildAss(kurz, { width: 1080, height: 1920 }), /\\fs/);
|
||||
});
|
||||
|
||||
test('libass darf selbst umbrechen', () => {
|
||||
// WrapStyle 2 bricht nur an \N um - buildAss setzt aber nie eines.
|
||||
// Damit war jede zu lange Zeile ein Ueberlauf statt eines Umbruchs.
|
||||
const ass = buildAss(toLines([w('test', 0, 1)]), { width: 1080, height: 1920 });
|
||||
assert.match(ass, /^WrapStyle: 0$/m);
|
||||
});
|
||||
|
||||
// ------------------------------------------------------------ ASS-Ausgabe
|
||||
|
||||
test('Zeilen ueberlappen sich nie', () => {
|
||||
|
||||
Reference in new issue
Block a user