mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-03-26 00:26:05 -07:00
chore: prepare v0.9.3 release
This commit is contained in:
@@ -1,5 +1,14 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## v0.9.3 (2026-03-25)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Launcher: Moved YouTube primary subtitle language defaults to `youtube.primarySubLanguages`.
|
||||||
|
- Launcher: Removed the placeholder YouTube subtitle retime step and now uses downloaded primary subtitle tracks directly, so there is no fake path rewrite before playback/sidebar loading.
|
||||||
|
- YouTube: Removed the `src/core/services/youtube/retime` helper and its tests after retiring the internal retime strategy.
|
||||||
|
- Docs: Clarified optional `alass` / `ffsubsync` subtitle-sync requirements and setup steps, including fallback behavior when sync tools are absent.
|
||||||
|
- Launcher: Removed the old `youtubeSubgen.primarySubLanguages` config path from the generated config and docs.
|
||||||
|
|
||||||
## v0.9.2 (2026-03-25)
|
## v0.9.2 (2026-03-25)
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ Local stats dashboard — watch time, anime library, vocabulary growth, mining t
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><b>alass / ffsubsync</b></td>
|
<td><b>alass / ffsubsync</b></td>
|
||||||
<td>Automatic subtitle retiming</td>
|
<td>Automatic subtitle retiming — requires <code>alass</code> or <code>ffsubsync</code> on your <code>PATH</code> (optional; subtitle syncing is disabled without them)</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><b>WebSocket</b></td>
|
<td><b>WebSocket</b></td>
|
||||||
@@ -105,7 +105,7 @@ Local stats dashboard — watch time, anime library, vocabulary growth, mining t
|
|||||||
| | Required | Optional |
|
| | Required | Optional |
|
||||||
| -------------- | --------------------------------------- | -------------------------------------- |
|
| -------------- | --------------------------------------- | -------------------------------------- |
|
||||||
| **Player** | [`mpv`](https://mpv.io) with IPC socket | — |
|
| **Player** | [`mpv`](https://mpv.io) with IPC socket | — |
|
||||||
| **Processing** | `ffmpeg`, `mecab` + `mecab-ipadic` | `guessit` (AniSkip) |
|
| **Processing** | `ffmpeg`, `mecab` + `mecab-ipadic` | `guessit` (AniSkip), `alass` / `ffsubsync` (subtitle sync) |
|
||||||
| **Media** | — | `yt-dlp`, `chafa`, `ffmpegthumbnailer` |
|
| **Media** | — | `yt-dlp`, `chafa`, `ffmpegthumbnailer` |
|
||||||
| **Selection** | — | `fzf` / `rofi` |
|
| **Selection** | — | `fzf` / `rofi` |
|
||||||
|
|
||||||
@@ -125,6 +125,8 @@ Local stats dashboard — watch time, anime library, vocabulary growth, mining t
|
|||||||
paru -S --needed mpv ffmpeg mecab-git mecab-ipadic
|
paru -S --needed mpv ffmpeg mecab-git mecab-ipadic
|
||||||
# Optional
|
# Optional
|
||||||
paru -S --needed yt-dlp fzf rofi chafa ffmpegthumbnailer xdotool xorg-xwininfo
|
paru -S --needed yt-dlp fzf rofi chafa ffmpegthumbnailer xdotool xorg-xwininfo
|
||||||
|
# Optional: subtitle sync (install at least one for subtitle syncing to work)
|
||||||
|
paru -S --needed alass python-ffsubsync
|
||||||
# X11 / XWAYLAND
|
# X11 / XWAYLAND
|
||||||
paru -S --needed xdotool xorg-xwininfo
|
paru -S --needed xdotool xorg-xwininfo
|
||||||
```
|
```
|
||||||
@@ -138,6 +140,9 @@ paru -S --needed xdotool xorg-xwininfo
|
|||||||
brew install mpv ffmpeg mecab mecab-ipadic
|
brew install mpv ffmpeg mecab mecab-ipadic
|
||||||
# Optional
|
# Optional
|
||||||
brew install yt-dlp fzf rofi chafa ffmpegthumbnailer
|
brew install yt-dlp fzf rofi chafa ffmpegthumbnailer
|
||||||
|
# Optional: subtitle sync (install at least one for subtitle syncing to work)
|
||||||
|
brew install alass
|
||||||
|
pip install ffsubsync
|
||||||
```
|
```
|
||||||
|
|
||||||
Grant Accessibility permission to SubMiner in **System Settings > Privacy & Security > Accessibility**.
|
Grant Accessibility permission to SubMiner in **System Settings > Privacy & Security > Accessibility**.
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
---
|
||||||
|
id: TASK-236
|
||||||
|
title: Gate Jimaku and SubSync modal actions when setup is missing
|
||||||
|
status: To Do
|
||||||
|
assignee: []
|
||||||
|
created_date: '2026-03-26 05:48'
|
||||||
|
labels:
|
||||||
|
- ui
|
||||||
|
- setup-validation
|
||||||
|
dependencies: []
|
||||||
|
---
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||||
|
Add safeguards in the Jimaku and SubSync modals so users cannot proceed with unsupported flows when required setup is missing. SubSync should clearly block use when alass/ffsubsync detection fails. Jimaku should surface a visible warning when no API key is configured and prevent proceeding with actions that require it.
|
||||||
|
<!-- SECTION:DESCRIPTION:END -->
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
<!-- AC:BEGIN -->
|
||||||
|
- [ ] #1 SubSync modal detects availability of alass and ffsubsync before enabling related action option
|
||||||
|
- [ ] #2 When only one of alass/ffsubsync is available, only the available path is selectable and clearly labeled; unavailable options are visually disabled
|
||||||
|
- [ ] #3 When neither alass nor ffsubsync are available, the unsupported action option is disabled and/or hidden, and cannot navigate to next step
|
||||||
|
- [ ] #4 If a user tries to proceed while detection says unavailable, submission is blocked with explanatory inline feedback
|
||||||
|
- [ ] #5 Jimaku modal detects missing API key (or invalid/missing key) and shows an immediate warning on search results or related UI area
|
||||||
|
- [ ] #6 When Jimaku API key is absent, actions that require key-based operations are disabled or blocked and cannot be submitted
|
||||||
|
- [ ] #7 All new/updated UX states include clear copy explaining what to fix (e.g., install binary, add API key, restart if needed)
|
||||||
|
- [ ] #8 Add or update tests for setup detection and blocked-state behavior in relevant modal/components
|
||||||
|
<!-- AC:END -->
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
---
|
||||||
|
id: TASK-237
|
||||||
|
title: Improve config validation error reporting and logging
|
||||||
|
status: To Do
|
||||||
|
assignee: []
|
||||||
|
created_date: '2026-03-26 05:51'
|
||||||
|
labels:
|
||||||
|
- errors
|
||||||
|
- config
|
||||||
|
- validation
|
||||||
|
- ux
|
||||||
|
dependencies: []
|
||||||
|
references:
|
||||||
|
- /docs/README.md
|
||||||
|
- /docs/workflow/verification.md
|
||||||
|
---
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||||
|
Replace raw error body system notifications during config validation with clearer, user-friendly summaries while retaining full technical detail in logs. The flow should surface what is wrong, where, and how to fix it without overloading the user with raw stack traces, and write structured details to console/file logs.
|
||||||
|
<!-- SECTION:DESCRIPTION:END -->
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
<!-- AC:BEGIN -->
|
||||||
|
- [ ] #1 When config validation fails, show a user-facing notification with cleaned, human-readable summary instead of dumping raw error text directly
|
||||||
|
- [ ] #2 Notification content includes actionable context (what field/setting failed, expected format/type, and next steps where possible)
|
||||||
|
- [ ] #3 Raw technical error details are preserved in console logs in a consistently formatted, presentable way
|
||||||
|
- [ ] #4 Config validation failures also write to persistent log file output in the same presentable format
|
||||||
|
- [ ] #5 When validation fails repeatedly or with multiple errors, aggregate and group errors for easier reading instead of showing one opaque blob
|
||||||
|
- [ ] #6 Warning/error notification should map to the specific invalid config section so users can jump to/identify what to fix
|
||||||
|
- [ ] #7 Add/update tests (unit/integration) that assert notification formatting and logging behavior for at least one malformed config case
|
||||||
|
- [ ] #8 No sensitive data (API keys/secrets) is written to logs/notifications when sanitizing errors
|
||||||
|
<!-- AC:END -->
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
type: changed
|
|
||||||
area: launcher
|
|
||||||
|
|
||||||
Moved YouTube primary subtitle language defaults to `youtube.primarySubLanguages`.
|
|
||||||
Removed the old `youtubeSubgen.primarySubLanguages` config path from the generated config and docs.
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "subminer",
|
"name": "subminer",
|
||||||
"version": "0.9.2",
|
"version": "0.9.3",
|
||||||
"description": "All-in-one sentence mining overlay with AnkiConnect and dictionary integration",
|
"description": "All-in-one sentence mining overlay with AnkiConnect and dictionary integration",
|
||||||
"packageManager": "bun@1.3.5",
|
"packageManager": "bun@1.3.5",
|
||||||
"main": "dist/main-entry.js",
|
"main": "dist/main-entry.js",
|
||||||
|
|||||||
@@ -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 });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -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)' : ''}`,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
12
src/main.ts
12
src/main.ts
@@ -317,7 +317,6 @@ import {
|
|||||||
acquireYoutubeSubtitleTracks,
|
acquireYoutubeSubtitleTracks,
|
||||||
} from './core/services/youtube/generate';
|
} from './core/services/youtube/generate';
|
||||||
import { resolveYoutubePlaybackUrl } from './core/services/youtube/playback-resolve';
|
import { resolveYoutubePlaybackUrl } from './core/services/youtube/playback-resolve';
|
||||||
import { retimeYoutubeSubtitle } from './core/services/youtube/retime';
|
|
||||||
import { probeYoutubeTracks } from './core/services/youtube/track-probe';
|
import { probeYoutubeTracks } from './core/services/youtube/track-probe';
|
||||||
import { startStatsServer } from './core/services/stats-server';
|
import { startStatsServer } from './core/services/stats-server';
|
||||||
import { registerStatsOverlayToggle, destroyStatsWindow } from './core/services/stats-window.js';
|
import { registerStatsOverlayToggle, destroyStatsWindow } from './core/services/stats-window.js';
|
||||||
@@ -824,17 +823,6 @@ const youtubeFlowRuntime = createYoutubeFlowRuntime({
|
|||||||
probeYoutubeTracks: (url: string) => probeYoutubeTracks(url),
|
probeYoutubeTracks: (url: string) => probeYoutubeTracks(url),
|
||||||
acquireYoutubeSubtitleTrack: (input) => acquireYoutubeSubtitleTrack(input),
|
acquireYoutubeSubtitleTrack: (input) => acquireYoutubeSubtitleTrack(input),
|
||||||
acquireYoutubeSubtitleTracks: (input) => acquireYoutubeSubtitleTracks(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) => {
|
openPicker: async (payload) => {
|
||||||
return await openYoutubeTrackPicker(
|
return await openYoutubeTrackPicker(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ test('youtube flow can open a manual picker session and load the selected subtit
|
|||||||
acquireYoutubeSubtitleTrack: async ({ track }) => ({
|
acquireYoutubeSubtitleTrack: async ({ track }) => ({
|
||||||
path: `/tmp/${track.id.replace(/[^a-z0-9_-]+/gi, '-')}.vtt`,
|
path: `/tmp/${track.id.replace(/[^a-z0-9_-]+/gi, '-')}.vtt`,
|
||||||
}),
|
}),
|
||||||
retimeYoutubePrimaryTrack: async ({ primaryPath }) => `${primaryPath}.retimed`,
|
|
||||||
openPicker: async (payload) => {
|
openPicker: async (payload) => {
|
||||||
openedPayloads.push(payload);
|
openedPayloads.push(payload);
|
||||||
queueMicrotask(() => {
|
queueMicrotask(() => {
|
||||||
@@ -75,7 +74,7 @@ test('youtube flow can open a manual picker session and load the selected subtit
|
|||||||
lang: 'ja-orig',
|
lang: 'ja-orig',
|
||||||
title: 'primary',
|
title: 'primary',
|
||||||
external: true,
|
external: true,
|
||||||
'external-filename': '/tmp/auto-ja-orig.vtt.retimed',
|
'external-filename': '/tmp/auto-ja-orig.vtt',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'sub',
|
type: 'sub',
|
||||||
@@ -135,7 +134,7 @@ test('youtube flow can open a manual picker session and load the selected subtit
|
|||||||
commands.some(
|
commands.some(
|
||||||
(command) =>
|
(command) =>
|
||||||
command[0] === 'sub-add' &&
|
command[0] === 'sub-add' &&
|
||||||
command[1] === '/tmp/auto-ja-orig.vtt.retimed' &&
|
command[1] === '/tmp/auto-ja-orig.vtt' &&
|
||||||
command[2] === 'select',
|
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']);
|
assert.deepEqual(focusOverlayCalls, ['focus-overlay']);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -179,7 +178,6 @@ test('youtube flow retries secondary after partial batch subtitle failure', asyn
|
|||||||
acquireSingleCalls.push(track.id);
|
acquireSingleCalls.push(track.id);
|
||||||
return { path: `/tmp/${track.id}.vtt` };
|
return { path: `/tmp/${track.id}.vtt` };
|
||||||
},
|
},
|
||||||
retimeYoutubePrimaryTrack: async ({ primaryPath }) => primaryPath,
|
|
||||||
openPicker: async (payload) => {
|
openPicker: async (payload) => {
|
||||||
queueMicrotask(() => {
|
queueMicrotask(() => {
|
||||||
void runtime.resolveActivePicker({
|
void runtime.resolveActivePicker({
|
||||||
@@ -281,7 +279,6 @@ test('youtube flow reports probe failure through the configured reporter in manu
|
|||||||
},
|
},
|
||||||
acquireYoutubeSubtitleTracks: async () => new Map(),
|
acquireYoutubeSubtitleTracks: async () => new Map(),
|
||||||
acquireYoutubeSubtitleTrack: async () => ({ path: '/tmp/unused.vtt' }),
|
acquireYoutubeSubtitleTrack: async () => ({ path: '/tmp/unused.vtt' }),
|
||||||
retimeYoutubePrimaryTrack: async ({ primaryPath }) => primaryPath,
|
|
||||||
openPicker: async () => true,
|
openPicker: async () => true,
|
||||||
pauseMpv: () => {},
|
pauseMpv: () => {},
|
||||||
resumeMpv: () => {},
|
resumeMpv: () => {},
|
||||||
@@ -322,7 +319,6 @@ test('youtube flow does not report failure when subtitle track binds before cue
|
|||||||
}),
|
}),
|
||||||
acquireYoutubeSubtitleTracks: async () => new Map(),
|
acquireYoutubeSubtitleTracks: async () => new Map(),
|
||||||
acquireYoutubeSubtitleTrack: async () => ({ path: '/tmp/auto-ja-orig.vtt' }),
|
acquireYoutubeSubtitleTrack: async () => ({ path: '/tmp/auto-ja-orig.vtt' }),
|
||||||
retimeYoutubePrimaryTrack: async ({ primaryPath }) => primaryPath,
|
|
||||||
openPicker: async (payload) => {
|
openPicker: async (payload) => {
|
||||||
queueMicrotask(() => {
|
queueMicrotask(() => {
|
||||||
void runtime.resolveActivePicker({
|
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(),
|
acquireYoutubeSubtitleTracks: async () => new Map(),
|
||||||
acquireYoutubeSubtitleTrack: async () => ({ path: '/tmp/auto-ja-orig.vtt' }),
|
acquireYoutubeSubtitleTrack: async () => ({ path: '/tmp/auto-ja-orig.vtt' }),
|
||||||
retimeYoutubePrimaryTrack: async ({ primaryPath }) => primaryPath,
|
|
||||||
openPicker: async (payload) => {
|
openPicker: async (payload) => {
|
||||||
queueMicrotask(() => {
|
queueMicrotask(() => {
|
||||||
void runtime.resolveActivePicker({
|
void runtime.resolveActivePicker({
|
||||||
@@ -464,7 +459,6 @@ test('youtube flow retries secondary subtitle selection until mpv reports the ex
|
|||||||
acquireYoutubeSubtitleTrack: async ({ track }) => ({
|
acquireYoutubeSubtitleTrack: async ({ track }) => ({
|
||||||
path: `/tmp/${track.id.replace(/[^a-z0-9_-]+/gi, '-')}.vtt`,
|
path: `/tmp/${track.id.replace(/[^a-z0-9_-]+/gi, '-')}.vtt`,
|
||||||
}),
|
}),
|
||||||
retimeYoutubePrimaryTrack: async ({ primaryPath }) => primaryPath,
|
|
||||||
openPicker: async (payload) => {
|
openPicker: async (payload) => {
|
||||||
queueMicrotask(() => {
|
queueMicrotask(() => {
|
||||||
void runtime.resolveActivePicker({
|
void runtime.resolveActivePicker({
|
||||||
@@ -568,7 +562,6 @@ test('youtube flow reuses the matching existing manual secondary track instead o
|
|||||||
acquireYoutubeSubtitleTrack: async ({ track }) => ({
|
acquireYoutubeSubtitleTrack: async ({ track }) => ({
|
||||||
path: `/tmp/${track.id.replace(/[^a-z0-9_-]+/gi, '-')}.vtt`,
|
path: `/tmp/${track.id.replace(/[^a-z0-9_-]+/gi, '-')}.vtt`,
|
||||||
}),
|
}),
|
||||||
retimeYoutubePrimaryTrack: async ({ primaryPath }) => primaryPath,
|
|
||||||
openPicker: async (payload) => {
|
openPicker: async (payload) => {
|
||||||
queueMicrotask(() => {
|
queueMicrotask(() => {
|
||||||
void runtime.resolveActivePicker({
|
void runtime.resolveActivePicker({
|
||||||
@@ -678,7 +671,6 @@ test('youtube flow leaves non-authoritative youtube subtitle tracks untouched af
|
|||||||
acquireYoutubeSubtitleTrack: async ({ track }) => ({
|
acquireYoutubeSubtitleTrack: async ({ track }) => ({
|
||||||
path: `/tmp/${track.id.replace(/[^a-z0-9_-]+/gi, '-')}.vtt`,
|
path: `/tmp/${track.id.replace(/[^a-z0-9_-]+/gi, '-')}.vtt`,
|
||||||
}),
|
}),
|
||||||
retimeYoutubePrimaryTrack: async ({ primaryPath }) => primaryPath,
|
|
||||||
openPicker: async (payload) => {
|
openPicker: async (payload) => {
|
||||||
queueMicrotask(() => {
|
queueMicrotask(() => {
|
||||||
void runtime.resolveActivePicker({
|
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');
|
throw new Error('should not download secondary track when manual english already exists');
|
||||||
},
|
},
|
||||||
retimeYoutubePrimaryTrack: async ({ primaryPath }) => primaryPath,
|
|
||||||
openPicker: async (payload) => {
|
openPicker: async (payload) => {
|
||||||
queueMicrotask(() => {
|
queueMicrotask(() => {
|
||||||
void runtime.resolveActivePicker({
|
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');
|
throw new Error('should not download secondary track when manual english appears in mpv');
|
||||||
},
|
},
|
||||||
retimeYoutubePrimaryTrack: async ({ primaryPath }) => primaryPath,
|
|
||||||
openPicker: async (payload) => {
|
openPicker: async (payload) => {
|
||||||
queueMicrotask(() => {
|
queueMicrotask(() => {
|
||||||
void runtime.resolveActivePicker({
|
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');
|
throw new Error('should not download secondary track when existing manual english track is reusable');
|
||||||
},
|
},
|
||||||
retimeYoutubePrimaryTrack: async ({ primaryPath }) => primaryPath,
|
|
||||||
openPicker: async () => false,
|
openPicker: async () => false,
|
||||||
pauseMpv: () => {},
|
pauseMpv: () => {},
|
||||||
resumeMpv: () => {},
|
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' };
|
return { path: '/tmp/auto-ja-orig.ja-orig.vtt' };
|
||||||
},
|
},
|
||||||
retimeYoutubePrimaryTrack: async ({ primaryPath }) => primaryPath,
|
|
||||||
openPicker: async () => false,
|
openPicker: async () => false,
|
||||||
pauseMpv: () => {},
|
pauseMpv: () => {},
|
||||||
resumeMpv: () => {},
|
resumeMpv: () => {},
|
||||||
|
|||||||
@@ -26,13 +26,6 @@ type YoutubeFlowDeps = {
|
|||||||
probeYoutubeTracks: (url: string) => Promise<YoutubeTrackProbeResult>;
|
probeYoutubeTracks: (url: string) => Promise<YoutubeTrackProbeResult>;
|
||||||
acquireYoutubeSubtitleTrack: typeof acquireYoutubeSubtitleTrack;
|
acquireYoutubeSubtitleTrack: typeof acquireYoutubeSubtitleTrack;
|
||||||
acquireYoutubeSubtitleTracks: typeof acquireYoutubeSubtitleTracks;
|
acquireYoutubeSubtitleTracks: typeof acquireYoutubeSubtitleTracks;
|
||||||
retimeYoutubePrimaryTrack: (input: {
|
|
||||||
targetUrl: string;
|
|
||||||
primaryTrack: YoutubeTrackOption;
|
|
||||||
primaryPath: string;
|
|
||||||
secondaryTrack: YoutubeTrackOption | null;
|
|
||||||
secondaryPath: string | null;
|
|
||||||
}) => Promise<string>;
|
|
||||||
openPicker: YoutubeFlowOpenPicker;
|
openPicker: YoutubeFlowOpenPicker;
|
||||||
pauseMpv: () => void;
|
pauseMpv: () => void;
|
||||||
resumeMpv: () => void;
|
resumeMpv: () => void;
|
||||||
@@ -624,14 +617,7 @@ export function createYoutubeFlowRuntime(deps: YoutubeFlowDeps) {
|
|||||||
track: input.primaryTrack,
|
track: input.primaryTrack,
|
||||||
})
|
})
|
||||||
).path;
|
).path;
|
||||||
primarySidebarPath = await deps.retimeYoutubePrimaryTrack({
|
primarySidebarPath = primaryInjectedPath;
|
||||||
targetUrl: input.url,
|
|
||||||
primaryTrack: input.primaryTrack,
|
|
||||||
primaryPath: primaryInjectedPath,
|
|
||||||
secondaryTrack: input.secondaryTrack,
|
|
||||||
secondaryPath: null,
|
|
||||||
});
|
|
||||||
primaryInjectedPath = primarySidebarPath;
|
|
||||||
} else {
|
} else {
|
||||||
const acquired = await acquireSelectedTracks({
|
const acquired = await acquireSelectedTracks({
|
||||||
targetUrl: input.url,
|
targetUrl: input.url,
|
||||||
@@ -640,13 +626,7 @@ export function createYoutubeFlowRuntime(deps: YoutubeFlowDeps) {
|
|||||||
secondaryTrack: existingSecondaryTrackId === null ? input.secondaryTrack : null,
|
secondaryTrack: existingSecondaryTrackId === null ? input.secondaryTrack : null,
|
||||||
secondaryFailureLabel: input.secondaryFailureLabel,
|
secondaryFailureLabel: input.secondaryFailureLabel,
|
||||||
});
|
});
|
||||||
primarySidebarPath = await deps.retimeYoutubePrimaryTrack({
|
primarySidebarPath = acquired.primaryPath;
|
||||||
targetUrl: input.url,
|
|
||||||
primaryTrack: input.primaryTrack,
|
|
||||||
primaryPath: acquired.primaryPath,
|
|
||||||
secondaryTrack: input.secondaryTrack,
|
|
||||||
secondaryPath: acquired.secondaryPath,
|
|
||||||
});
|
|
||||||
primaryInjectedPath = primarySidebarPath;
|
primaryInjectedPath = primarySidebarPath;
|
||||||
secondaryInjectedPath = acquired.secondaryPath;
|
secondaryInjectedPath = acquired.secondaryPath;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user