chore: prepare v0.9.3 release

This commit is contained in:
2026-03-25 23:58:31 -07:00
parent 242402b253
commit 4c95b57885
11 changed files with 85 additions and 97 deletions

View File

@@ -1,29 +0,0 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { retimeYoutubeSubtitle } from './retime';
test('retimeYoutubeSubtitle uses the downloaded subtitle path as-is', async () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'subminer-youtube-retime-'));
try {
const primaryPath = path.join(root, 'primary.vtt');
const referencePath = path.join(root, 'reference.vtt');
fs.writeFileSync(primaryPath, 'WEBVTT\n', 'utf8');
fs.writeFileSync(referencePath, 'WEBVTT\n', 'utf8');
const result = await retimeYoutubeSubtitle({
primaryPath,
secondaryPath: referencePath,
});
assert.equal(result.ok, true);
assert.equal(result.strategy, 'none');
assert.equal(result.path, primaryPath);
assert.equal(result.message, 'Using downloaded subtitle as-is (no automatic retime enabled)');
assert.equal(fs.readFileSync(result.path, 'utf8'), 'WEBVTT\n');
} finally {
fs.rmSync(root, { recursive: true, force: true });
}
});

View File

@@ -1,11 +0,0 @@
export async function retimeYoutubeSubtitle(input: {
primaryPath: string;
secondaryPath: string | null;
}): Promise<{ ok: boolean; path: string; strategy: 'none' | 'alass' | 'ffsubsync'; message: string }> {
return {
ok: true,
path: input.primaryPath,
strategy: 'none',
message: `Using downloaded subtitle as-is${input.secondaryPath ? ' (no automatic retime enabled)' : ''}`,
};
}

View File

@@ -317,7 +317,6 @@ import {
acquireYoutubeSubtitleTracks,
} from './core/services/youtube/generate';
import { resolveYoutubePlaybackUrl } from './core/services/youtube/playback-resolve';
import { retimeYoutubeSubtitle } from './core/services/youtube/retime';
import { probeYoutubeTracks } from './core/services/youtube/track-probe';
import { startStatsServer } from './core/services/stats-server';
import { registerStatsOverlayToggle, destroyStatsWindow } from './core/services/stats-window.js';
@@ -824,17 +823,6 @@ const youtubeFlowRuntime = createYoutubeFlowRuntime({
probeYoutubeTracks: (url: string) => probeYoutubeTracks(url),
acquireYoutubeSubtitleTrack: (input) => acquireYoutubeSubtitleTrack(input),
acquireYoutubeSubtitleTracks: (input) => acquireYoutubeSubtitleTracks(input),
retimeYoutubePrimaryTrack: async ({ primaryTrack, primaryPath, secondaryTrack, secondaryPath }) => {
if (primaryTrack.kind !== 'auto') {
return primaryPath;
}
const result = await retimeYoutubeSubtitle({
primaryPath,
secondaryPath: secondaryTrack ? secondaryPath : null,
});
logger.info(`Using YouTube subtitle path: ${result.path} (${result.strategy})`);
return result.path;
},
openPicker: async (payload) => {
return await openYoutubeTrackPicker(
{

View File

@@ -46,7 +46,6 @@ test('youtube flow can open a manual picker session and load the selected subtit
acquireYoutubeSubtitleTrack: async ({ track }) => ({
path: `/tmp/${track.id.replace(/[^a-z0-9_-]+/gi, '-')}.vtt`,
}),
retimeYoutubePrimaryTrack: async ({ primaryPath }) => `${primaryPath}.retimed`,
openPicker: async (payload) => {
openedPayloads.push(payload);
queueMicrotask(() => {
@@ -75,7 +74,7 @@ test('youtube flow can open a manual picker session and load the selected subtit
lang: 'ja-orig',
title: 'primary',
external: true,
'external-filename': '/tmp/auto-ja-orig.vtt.retimed',
'external-filename': '/tmp/auto-ja-orig.vtt',
},
{
type: 'sub',
@@ -135,7 +134,7 @@ test('youtube flow can open a manual picker session and load the selected subtit
commands.some(
(command) =>
command[0] === 'sub-add' &&
command[1] === '/tmp/auto-ja-orig.vtt.retimed' &&
command[1] === '/tmp/auto-ja-orig.vtt' &&
command[2] === 'select',
),
);
@@ -157,7 +156,7 @@ test('youtube flow can open a manual picker session and load the selected subtit
),
),
);
assert.deepEqual(refreshedSidebarSources, ['/tmp/auto-ja-orig.vtt.retimed']);
assert.deepEqual(refreshedSidebarSources, ['/tmp/auto-ja-orig.vtt']);
assert.deepEqual(focusOverlayCalls, ['focus-overlay']);
});
@@ -179,7 +178,6 @@ test('youtube flow retries secondary after partial batch subtitle failure', asyn
acquireSingleCalls.push(track.id);
return { path: `/tmp/${track.id}.vtt` };
},
retimeYoutubePrimaryTrack: async ({ primaryPath }) => primaryPath,
openPicker: async (payload) => {
queueMicrotask(() => {
void runtime.resolveActivePicker({
@@ -281,7 +279,6 @@ test('youtube flow reports probe failure through the configured reporter in manu
},
acquireYoutubeSubtitleTracks: async () => new Map(),
acquireYoutubeSubtitleTrack: async () => ({ path: '/tmp/unused.vtt' }),
retimeYoutubePrimaryTrack: async ({ primaryPath }) => primaryPath,
openPicker: async () => true,
pauseMpv: () => {},
resumeMpv: () => {},
@@ -322,7 +319,6 @@ test('youtube flow does not report failure when subtitle track binds before cue
}),
acquireYoutubeSubtitleTracks: async () => new Map(),
acquireYoutubeSubtitleTrack: async () => ({ path: '/tmp/auto-ja-orig.vtt' }),
retimeYoutubePrimaryTrack: async ({ primaryPath }) => primaryPath,
openPicker: async (payload) => {
queueMicrotask(() => {
void runtime.resolveActivePicker({
@@ -389,7 +385,6 @@ test('youtube flow does not fail when mpv reports sub-text as unavailable after
}),
acquireYoutubeSubtitleTracks: async () => new Map(),
acquireYoutubeSubtitleTrack: async () => ({ path: '/tmp/auto-ja-orig.vtt' }),
retimeYoutubePrimaryTrack: async ({ primaryPath }) => primaryPath,
openPicker: async (payload) => {
queueMicrotask(() => {
void runtime.resolveActivePicker({
@@ -464,7 +459,6 @@ test('youtube flow retries secondary subtitle selection until mpv reports the ex
acquireYoutubeSubtitleTrack: async ({ track }) => ({
path: `/tmp/${track.id.replace(/[^a-z0-9_-]+/gi, '-')}.vtt`,
}),
retimeYoutubePrimaryTrack: async ({ primaryPath }) => primaryPath,
openPicker: async (payload) => {
queueMicrotask(() => {
void runtime.resolveActivePicker({
@@ -568,7 +562,6 @@ test('youtube flow reuses the matching existing manual secondary track instead o
acquireYoutubeSubtitleTrack: async ({ track }) => ({
path: `/tmp/${track.id.replace(/[^a-z0-9_-]+/gi, '-')}.vtt`,
}),
retimeYoutubePrimaryTrack: async ({ primaryPath }) => primaryPath,
openPicker: async (payload) => {
queueMicrotask(() => {
void runtime.resolveActivePicker({
@@ -678,7 +671,6 @@ test('youtube flow leaves non-authoritative youtube subtitle tracks untouched af
acquireYoutubeSubtitleTrack: async ({ track }) => ({
path: `/tmp/${track.id.replace(/[^a-z0-9_-]+/gi, '-')}.vtt`,
}),
retimeYoutubePrimaryTrack: async ({ primaryPath }) => primaryPath,
openPicker: async (payload) => {
queueMicrotask(() => {
void runtime.resolveActivePicker({
@@ -772,7 +764,6 @@ test('youtube flow reuses existing manual youtube subtitle tracks when both requ
}
throw new Error('should not download secondary track when manual english already exists');
},
retimeYoutubePrimaryTrack: async ({ primaryPath }) => primaryPath,
openPicker: async (payload) => {
queueMicrotask(() => {
void runtime.resolveActivePicker({
@@ -871,7 +862,6 @@ test('youtube flow waits for manual youtube tracks to appear before falling back
}
throw new Error('should not download secondary track when manual english appears in mpv');
},
retimeYoutubePrimaryTrack: async ({ primaryPath }) => primaryPath,
openPicker: async (payload) => {
queueMicrotask(() => {
void runtime.resolveActivePicker({
@@ -982,7 +972,6 @@ test('youtube flow reuses manual youtube tracks even when mpv exposes external f
}
throw new Error('should not download secondary track when existing manual english track is reusable');
},
retimeYoutubePrimaryTrack: async ({ primaryPath }) => primaryPath,
openPicker: async () => false,
pauseMpv: () => {},
resumeMpv: () => {},
@@ -1102,7 +1091,6 @@ test('youtube flow falls back to existing auto secondary track when auto seconda
}
return { path: '/tmp/auto-ja-orig.ja-orig.vtt' };
},
retimeYoutubePrimaryTrack: async ({ primaryPath }) => primaryPath,
openPicker: async () => false,
pauseMpv: () => {},
resumeMpv: () => {},

View File

@@ -26,13 +26,6 @@ type YoutubeFlowDeps = {
probeYoutubeTracks: (url: string) => Promise<YoutubeTrackProbeResult>;
acquireYoutubeSubtitleTrack: typeof acquireYoutubeSubtitleTrack;
acquireYoutubeSubtitleTracks: typeof acquireYoutubeSubtitleTracks;
retimeYoutubePrimaryTrack: (input: {
targetUrl: string;
primaryTrack: YoutubeTrackOption;
primaryPath: string;
secondaryTrack: YoutubeTrackOption | null;
secondaryPath: string | null;
}) => Promise<string>;
openPicker: YoutubeFlowOpenPicker;
pauseMpv: () => void;
resumeMpv: () => void;
@@ -624,14 +617,7 @@ export function createYoutubeFlowRuntime(deps: YoutubeFlowDeps) {
track: input.primaryTrack,
})
).path;
primarySidebarPath = await deps.retimeYoutubePrimaryTrack({
targetUrl: input.url,
primaryTrack: input.primaryTrack,
primaryPath: primaryInjectedPath,
secondaryTrack: input.secondaryTrack,
secondaryPath: null,
});
primaryInjectedPath = primarySidebarPath;
primarySidebarPath = primaryInjectedPath;
} else {
const acquired = await acquireSelectedTracks({
targetUrl: input.url,
@@ -640,13 +626,7 @@ export function createYoutubeFlowRuntime(deps: YoutubeFlowDeps) {
secondaryTrack: existingSecondaryTrackId === null ? input.secondaryTrack : null,
secondaryFailureLabel: input.secondaryFailureLabel,
});
primarySidebarPath = await deps.retimeYoutubePrimaryTrack({
targetUrl: input.url,
primaryTrack: input.primaryTrack,
primaryPath: acquired.primaryPath,
secondaryTrack: input.secondaryTrack,
secondaryPath: acquired.secondaryPath,
});
primarySidebarPath = acquired.primaryPath;
primaryInjectedPath = primarySidebarPath;
secondaryInjectedPath = acquired.secondaryPath;
}