Files
sing_detect/src/api.ts
youfu 4887981a2d v7: standalone Sing Detect app — full Tauri project tree
Promotes the v6 detection slice into a complete, buildable Tauri 2 app and
renames Stream Studio → Sing Detect (detection-only; video-concat split to its
own project). Proper src/ + src-tauri/ layout, Python sidecar under sidecar/,
app icons, package/Cargo manifests, and dev/build/pack scripts.

Pipeline unchanged in spirit: Rust drives ffmpeg to a 16 kHz mono WAV, the
Python sidecar (inaSpeechSegmenter) emits line-JSON segments, and Rust writes
EDL / markers CSV / CSV / JSON exports. Long ops run on spawn_blocking; cancel
kills the whole py -3.10 process tree.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 23:12:25 +08:00

44 lines
2.3 KiB
TypeScript

// Typed bridge to the Rust backend (detection-only build).
import { invoke } from "@tauri-apps/api/core";
import { listen, type UnlistenFn } from "@tauri-apps/api/event";
import { getCurrentWebview } from "@tauri-apps/api/webview";
import { open } from "@tauri-apps/plugin-dialog";
import { revealItemInDir } from "@tauri-apps/plugin-opener";
export interface Tools { ffmpeg: string | null; ffprobe: string | null; }
export interface Segment { index: number; start: number; end: number; duration: number; }
export interface DetectResult { segments: Segment[]; stats: Record<string, number>; }
export interface ExportResult { files: string[]; }
export interface DetectionStatus { python: boolean; packages: boolean; model: boolean; ready: boolean; detail: string; }
export interface SetupProgress { message: string; pct: number; }
export const getTools = () => invoke<Tools>("get_tools");
export const checkDetection = () => invoke<DetectionStatus>("check_detection");
export const setupDetection = () => invoke<void>("setup_detection");
export const detectSongs = (video: string, gap: number, minDuration: number, padding: number) =>
invoke<DetectResult>("detect_songs", { video, gap, minDuration, padding });
export const exportSegments = (segments: Segment[], sourceVideo: string, outDir: string | null, fps: number) =>
invoke<ExportResult>("export_segments", { segments, sourceVideo, outDir, fps });
export const cancel = () => invoke<void>("cancel");
export const onDetectProgress = (cb: (msg: string) => void): Promise<UnlistenFn> =>
listen<{ message: string }>("detect-progress", (e) => cb(e.payload.message));
export const onDetectSetupProgress = (cb: (p: SetupProgress) => void): Promise<UnlistenFn> =>
listen<SetupProgress>("detect-setup-progress", (e) => cb(e.payload));
export const onFilesDropped = (cb: (paths: string[]) => void): Promise<UnlistenFn> =>
getCurrentWebview().onDragDropEvent((e) => {
if (e.payload.type === "drop") cb(e.payload.paths);
});
export async function pickVideo(): Promise<string | null> {
const sel = await open({
multiple: false,
filters: [{ name: "Video", extensions: ["mp4", "mov", "mkv", "avi", "flv", "ts", "wmv", "m4v", "webm", "mts", "m2ts"] }],
});
return typeof sel === "string" ? sel : null;
}
export const reveal = (path: string) => revealItemInDir(path);