Recently Written · git

scan-subread

PDF reader that follows an audiobook with the SubRead suite

git clone https://github.com/equwal/scan-subread

Log | Files | Refs


commit 5be052b18632699f6292d2af434d481691ed6198
truex <truex@equwal.com>
2026-09-26 20:09:35 -0700

Remove four functions that the app no longer uses

waitUntil and seekArgument were ports from SubRead Overlay and the KOReader plugin; OverlayClock polls and rounds the seek itself. lastCueAt replaced cueIndexAt. The SubRead language check in book-subtitles.ts replaced whisperCode, and it also knows jpn+eng. Their tests go with them.

 src/book-text.ts          |  5 -----
 src/play-clock.ts         | 11 -----------
 src/player-state.ts       |  5 -----
 src/subtitles.ts          |  7 -------
 test/book-text.test.ts    | 11 +----------
 test/play-clock.test.ts   | 34 +---------------------------------
 test/player-state.test.ts | 11 +----------
 test/subtitles.test.ts    | 34 +---------------------------------
 8 files changed, 4 insertions(+), 114 deletions(-)
diff --git a/src/book-text.ts b/src/book-text.ts
index efc050a..d447771 100644
--- a/src/book-text.ts
+++ b/src/book-text.ts
@@ -84,8 +84,3 @@ export function bookText(tokens: readonly OcrToken[]): string {
   endPage();
   return pages.map((p) => p.join('\n')).join('\n\n') + (pages.length > 0 ? '\n' : '');
 }
-
-/** The Whisper language code for a tesseract language ("jpn", "jpn_vert", "jpn+eng", "eng"). */
-export function whisperCode(ocrLang: string): 'ja' | 'en' {
-  return ocrLang.startsWith('jpn') ? 'ja' : 'en';
-}
diff --git a/src/play-clock.ts b/src/play-clock.ts
index eb6cc8f..c6cb7ed 100644
--- a/src/play-clock.ts
+++ b/src/play-clock.ts
@@ -17,14 +17,3 @@ export function positionAt(clock: PlayClock, nowMs: number): number {
   if (!clock.playing) return clock.positionMs;
   return clock.positionMs + (nowMs - clock.reportedAtMs) * clock.speed;
 }
-
-/**
- * How long to wait, on the clock of the device, until the media is at
- * `targetMs`. Null when the media does not move forward, or is already there.
- */
-export function waitUntil(clock: PlayClock, targetMs: number, nowMs: number): number | null {
-  if (!clock.playing || clock.speed <= 0) return null;
-  const left = targetMs - positionAt(clock, nowMs);
-  if (left <= 0) return null;
-  return Math.ceil(left / clock.speed);
-}
diff --git a/src/player-state.ts b/src/player-state.ts
index e497c5c..1512a5c 100644
--- a/src/player-state.ts
+++ b/src/player-state.ts
@@ -51,8 +51,3 @@ export function parseState(line: string | null | undefined): PlayerState | Playe
     package: fields.get('package') ?? '',
   };
 }
-
-/** The argument of a seek call: the position in milliseconds, as a whole number. */
-export function seekArgument(ms: number): string {
-  return String(Math.max(0, Math.round(ms)));
-}
diff --git a/src/subtitles.ts b/src/subtitles.ts
index 4af23d2..1f6540a 100644
--- a/src/subtitles.ts
+++ b/src/subtitles.ts
@@ -89,10 +89,3 @@ export function lastCueAt(cues: readonly Pick<Cue, 'start'>[], t: number): numbe
   }
   return found;
 }
-
-/** Index of the cue active at time t, or -1. Cues must be sorted by start. */
-export function cueIndexAt(cues: readonly Cue[], t: number): number {
-  const found = lastCueAt(cues, t);
-  if (found >= 0 && t < cues[found]!.end) return found;
-  return -1;
-}
diff --git a/test/book-text.test.ts b/test/book-text.test.ts
index 3c8bec5..e9fc27c 100644
--- a/test/book-text.test.ts
+++ b/test/book-text.test.ts
@@ -1,7 +1,7 @@
 import fc from 'fast-check';
 import { describe, expect, it } from 'vitest';
 import type { OcrToken } from '../src/align';
-import { bookText, joinWords, whisperCode } from '../src/book-text';
+import { bookText, joinWords } from '../src/book-text';
 
 const box = { x0: 0, y0: 0, x1: 1, y1: 1 };
 
@@ -109,12 +109,3 @@ describe('bookText', () => {
     );
   });
 });
-
-describe('whisperCode', () => {
-  it('maps tesseract languages to Whisper codes', () => {
-    expect(whisperCode('jpn')).toBe('ja');
-    expect(whisperCode('jpn_vert')).toBe('ja');
-    expect(whisperCode('jpn+eng')).toBe('ja');
-    expect(whisperCode('eng')).toBe('en');
-  });
-});
diff --git a/test/play-clock.test.ts b/test/play-clock.test.ts
index cbfb9be..bc04ebe 100644
--- a/test/play-clock.test.ts
+++ b/test/play-clock.test.ts
@@ -1,6 +1,6 @@
 import fc from 'fast-check';
 import { describe, expect, it } from 'vitest';
-import { positionAt, waitUntil, type PlayClock } from '../src/play-clock';
+import { positionAt, type PlayClock } from '../src/play-clock';
 
 const ms = fc.nat({ max: 10 ** 8 });
 const speed = fc.double({ min: 0.25, max: 4, noNaN: true, noDefaultInfinity: true });
@@ -45,35 +45,3 @@ describe('positionAt', () => {
     );
   });
 });
-
-describe('waitUntil', () => {
-  it('gives the real time until the target', () => {
-    const c: PlayClock = { positionMs: 1000, reportedAtMs: 0, speed: 2, playing: true };
-    expect(waitUntil(c, 3000, 0)).toBe(1000);
-    expect(waitUntil(c, 3000, 500)).toBe(500);
-  });
-
-  it('gives null when paused or already past the target', () => {
-    const paused: PlayClock = { positionMs: 0, reportedAtMs: 0, speed: 1, playing: false };
-    expect(waitUntil(paused, 100, 0)).toBeNull();
-    const playing = { ...paused, playing: true };
-    expect(waitUntil(playing, 0, 0)).toBeNull();
-    expect(waitUntil(playing, -5, 0)).toBeNull();
-  });
-
-  it('lands on the target after the wait', () => {
-    fc.assert(
-      fc.property(clock, ms, ms, (c, now, target) => {
-        const playing = { ...c, playing: true };
-        const wait = waitUntil(playing, target, now);
-        if (wait === null) {
-          expect(positionAt(playing, now)).toBeGreaterThanOrEqual(target);
-        } else {
-          expect(wait).toBeGreaterThan(0);
-          // The wait is whole milliseconds; the float product can fall a hair short.
-          expect(positionAt(playing, now + wait)).toBeGreaterThanOrEqual(target - 1e-6);
-        }
-      }),
-    );
-  });
-});
diff --git a/test/player-state.test.ts b/test/player-state.test.ts
index 666cc26..6221538 100644
--- a/test/player-state.test.ts
+++ b/test/player-state.test.ts
@@ -1,6 +1,6 @@
 import fc from 'fast-check';
 import { describe, expect, it } from 'vitest';
-import { isPlayerError, parseState, seekArgument, type PlayerState } from '../src/player-state';
+import { isPlayerError, parseState, type PlayerState } from '../src/player-state';
 
 function state(line: string): PlayerState {
   const parsed = parseState(line);
@@ -54,15 +54,6 @@ describe('parseState', () => {
     expect(state('playing=1;position=5').package).toBe('');
   });
 
-  it('round trips a position through the seek argument', () => {
-    for (const ms of [0, 1, 1000, 96153, 3600500, 12345678.9]) {
-      const argument = seekArgument(ms);
-      expect(argument).toMatch(/^\d+$/);
-      expect(state(`playing=0;position=${argument}`).positionMs).toBeCloseTo(ms, -1);
-    }
-    expect(seekArgument(-3)).toBe('0');
-  });
-
   it('round trips any state line', () => {
     const pkg = fc.stringMatching(/^[a-z][a-z0-9_.]{0,30}$/);
     const speed = fc.double({ min: 0.25, max: 4, noNaN: true, noDefaultInfinity: true });
diff --git a/test/subtitles.test.ts b/test/subtitles.test.ts
index 31d7259..355cf76 100644
--- a/test/subtitles.test.ts
+++ b/test/subtitles.test.ts
@@ -1,6 +1,6 @@
 import fc from 'fast-check';
 import { describe, expect, it } from 'vitest';
-import { cueIndexAt, decodeSubtitles, lastCueAt, parseSubtitles } from '../src/subtitles';
+import { decodeSubtitles, lastCueAt, parseSubtitles } from '../src/subtitles';
 
 const SRT = `1
 00:00:01,000 --> 00:00:03,500
@@ -109,38 +109,6 @@ describe('decodeSubtitles', () => {
   });
 });
 
-describe('cueIndexAt', () => {
-  const cues = parseSubtitles(SRT);
-
-  it('finds the active cue and returns -1 in gaps', () => {
-    expect(cueIndexAt(cues, 0.5)).toBe(-1);
-    expect(cueIndexAt(cues, 1)).toBe(0);
-    expect(cueIndexAt(cues, 3.49)).toBe(0);
-    expect(cueIndexAt(cues, 3.5)).toBe(1);
-    expect(cueIndexAt(cues, 6)).toBe(-1);
-  });
-
-  it('agrees with a linear scan over non-overlapping cues', () => {
-    // Each item is (gap before the cue, cue length).
-    const gapsAndLengths = fc.array(fc.tuple(fc.nat({ max: 20 }), fc.nat({ max: 20 })), {
-      maxLength: 30,
-    });
-    fc.assert(
-      fc.property(gapsAndLengths, fc.nat({ max: 1300 }), (items, t) => {
-        let clock = 0;
-        const sorted = items.map(([gap, len]) => {
-          const start = clock + gap;
-          clock = start + len;
-          return { start, end: clock, text: 'x' };
-        });
-        const got = cueIndexAt(sorted, t);
-        const linear = sorted.findIndex((c) => c.start <= t && t < c.end);
-        expect(got).toBe(linear);
-      }),
-    );
-  });
-});
-
 describe('lastCueAt', () => {
   const cues = [
     { start: 1, end: 2 },