From a773d791a710ca2ca0fcc4ffc44da385af69ed03 Mon Sep 17 00:00:00 2001 From: Patryk Gensch <43010113+patryk025@users.noreply.github.com> Date: Fri, 21 Aug 2026 16:30:14 +0200 Subject: [PATCH] Drop Whisper's invented subtitle credits from transcripts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Measured on 64 real clips with ggml-medium: 54 came back as genuine Polish, 9 as bracketed non-speech markers that are accurate ([muzyka] on music), and one as "Napisy stworzone przez społeczność Amara.org" — a credit line Whisper learned from training data and emits over silence. That is not a record of what is on the disc, so it does not belong in an archival catalogue. Bracketed markers stay: they describe the recording truthfully. Co-Authored-By: Claude Opus 5 --- .../rexcatalog/transcribe/Transcriber.java | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/src/main/java/pl/genschu/rexcatalog/transcribe/Transcriber.java b/src/main/java/pl/genschu/rexcatalog/transcribe/Transcriber.java index cd93130..f4b6d4e 100644 --- a/src/main/java/pl/genschu/rexcatalog/transcribe/Transcriber.java +++ b/src/main/java/pl/genschu/rexcatalog/transcribe/Transcriber.java @@ -52,6 +52,17 @@ public final class Transcriber { */ private static final String THREADS = System.getProperty("catalog.whisper.threads", "4"); + /** + * Zdania, które Whisper dopisuje z siebie na ciszy i muzyce — podpisy ekip + * tworzących napisy, wyuczone z materiału treningowego. To nie jest zapis + * tego, co słychać, tylko wymysł modelu, więc do katalogu archiwalnego + * trafić nie może. Znaczniki w rodzaju {@code [muzyka]} zostawiamy: one + * opisują nagranie zgodnie z prawdą. + */ + private static final List WYMYSLY = List.of( + "napisy stworzone przez", "napisy: ", "subtitles by", "amara.org", + "zdjęcia i napisy", "dziękuję za uwagę", "thanks for watching"); + /** Nazwy, pod którymi whisper.cpp bywa instalowany. */ private static final List BINARIES = List.of("whisper-cli", "whisper-cpp", "whisper", "main"); @@ -276,7 +287,7 @@ public final class Transcriber { for (int i = 0; i < ready.size(); i++) { try { - String text = readResult(inputs.get(i)); + String text = withoutHallucinations(readResult(inputs.get(i))); synchronized (lock) { // pusty wynik też zapisujemy: cisza albo sam efekt dźwiękowy, // inaczej ten plik wracałby przy każdym kolejnym przebiegu @@ -328,6 +339,25 @@ public final class Transcriber { } } + /** + * Usuwa zdania, których model nie usłyszał, tylko je dopisał. Jeśli po + * odsianiu nie zostaje nic, zapisujemy pustkę — plik jest wtedy oznaczony + * jako przerobiony i nie wraca przy kolejnym przebiegu. + */ + static String withoutHallucinations(String text) { + if (text == null || text.isBlank()) { + return ""; + } + StringBuilder out = new StringBuilder(); + for (String line : text.split("\n")) { + String probe = line.toLowerCase(Locale.ROOT); + if (WYMYSLY.stream().noneMatch(probe::contains)) { + out.append(line).append('\n'); + } + } + return out.toString().trim(); + } + /** whisper.cpp dopisuje rozszerzenie do pełnej nazwy wejścia: klip3.wav → klip3.wav.txt. */ private static Path resultPath(Path input) { return input.resolveSibling(input.getFileName() + ".txt");