mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-08-03 07:21:34 -07:00
c5140b0f7b
- Add `subminer anime` / `--anime` and a tray entry to open a browser that searches installed Aniyomi extension sources, shows cover art and episodes, and plays into mpv with overlay/mining attached - Add an Extensions tab to add repos and install/update/remove sources, and per-source settings for sources needing config - Support searching all sources at once with streaming, per-source results and status - Prefer Japanese audio/subtitle tracks from the source and keep the primary subtitle slot reserved for Japanese - Fix window/tray/Dock handling so the browser and mpv can be switched between without quitting the app or losing the Dock icon - Add anime.repos, anime.extensionsDir, anime.preferredQuality config keys (no bundled repos or discovery)
55 lines
1.9 KiB
TypeScript
55 lines
1.9 KiB
TypeScript
import type { BridgeVideo, OkHttpHeaders, ResolvedStream } from './types';
|
|
|
|
/**
|
|
* Flatten OkHttp's alternating `[name, value, name, value]` array into a map.
|
|
* A trailing name with no value is dropped rather than mapped to undefined.
|
|
*/
|
|
export function parseOkHttpHeaders(headers: OkHttpHeaders | undefined): Record<string, string> {
|
|
const flat = headers?.['namesAndValues$okhttp'];
|
|
if (!Array.isArray(flat)) return {};
|
|
|
|
const parsed: Record<string, string> = {};
|
|
for (let i = 0; i + 1 < flat.length; i += 2) {
|
|
const name = flat[i];
|
|
const value = flat[i + 1];
|
|
if (typeof name === 'string' && typeof value === 'string') parsed[name] = value;
|
|
}
|
|
return parsed;
|
|
}
|
|
|
|
/**
|
|
* Render headers as mpv's `--http-header-fields` string list. mpv splits
|
|
* entries on commas, so commas inside a value must be escaped.
|
|
*/
|
|
export function toMpvHeaderFields(headers: Record<string, string>): string {
|
|
return Object.entries(headers)
|
|
.map(([name, value]) => `${name}: ${value.replace(/,/g, '\\,')}`)
|
|
.join(',');
|
|
}
|
|
|
|
function normalizeTracks(
|
|
tracks: Array<{ url?: string; lang?: string }> | undefined,
|
|
): Array<{ url: string; lang: string }> {
|
|
if (!Array.isArray(tracks)) return [];
|
|
return tracks
|
|
.filter((track): track is { url: string; lang?: string } => typeof track.url === 'string')
|
|
.map((track) => ({ url: track.url, lang: track.lang ?? '' }));
|
|
}
|
|
|
|
/**
|
|
* Normalize a bridge video into a playable stream. Returns null when the
|
|
* extension produced no `videoUrl`, which happens for entries it failed to
|
|
* resolve.
|
|
*/
|
|
export function resolveStream(video: BridgeVideo): ResolvedStream | null {
|
|
if (typeof video.videoUrl !== 'string' || video.videoUrl.length === 0) return null;
|
|
|
|
return {
|
|
url: video.videoUrl,
|
|
quality: video.quality ?? '',
|
|
headers: parseOkHttpHeaders(video.headers),
|
|
subtitles: normalizeTracks(video.subtitleTracks),
|
|
audios: normalizeTracks(video.audioTracks),
|
|
};
|
|
}
|