103 lines
3.4 KiB
TypeScript
103 lines
3.4 KiB
TypeScript
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
import {
|
|
type SubtitleApplyRequest,
|
|
SubtitleApplyResult,
|
|
type SubtitleFetchRequest,
|
|
SubtitleParseResponse,
|
|
type SubtitleSearchRequest,
|
|
SubtitleSearchResponse,
|
|
SubtitleTargetsResponse,
|
|
SubtitleWritesResponse,
|
|
SubtitleWriteView,
|
|
} from "@umt/contracts";
|
|
import { api } from "~/lib/api.ts";
|
|
import { getParsed, parseOrThrow } from "./library.ts";
|
|
|
|
// 补字幕(docs/plan/subtitles.md):目标视频、解析上传、写入、历史与撤销
|
|
|
|
export const subtitleKeys = {
|
|
targets: (entityId: string) => ["subtitles", "targets", entityId] as const,
|
|
writes: (entityId: string) => ["subtitles", "writes", entityId] as const,
|
|
};
|
|
|
|
export function useSubtitleTargets(entityId: string, enabled = true) {
|
|
return useQuery({
|
|
queryKey: subtitleKeys.targets(entityId),
|
|
enabled,
|
|
queryFn: ({ signal }) => getParsed(SubtitleTargetsResponse, "/subtitles/targets", { entityId }, signal),
|
|
});
|
|
}
|
|
|
|
export function useSubtitleWrites(entityId: string, enabled = true) {
|
|
return useQuery({
|
|
queryKey: subtitleKeys.writes(entityId),
|
|
enabled,
|
|
queryFn: ({ signal }) => getParsed(SubtitleWritesResponse, "/subtitles/writes", { entityId }, signal),
|
|
});
|
|
}
|
|
|
|
/** 读本地文件为 base64(字幕与 zip 都很小,直接放进 JSON) */
|
|
async function fileBase64(f: File): Promise<string> {
|
|
const buf = new Uint8Array(await f.arrayBuffer());
|
|
let s = "";
|
|
for (let i = 0; i < buf.length; i += 0x8000) s += String.fromCharCode(...buf.subarray(i, i + 0x8000));
|
|
return btoa(s);
|
|
}
|
|
|
|
export function useParseSubtitles() {
|
|
return useMutation({
|
|
mutationFn: async ({ entityId, files }: { entityId: string; files: File[] }) =>
|
|
parseOrThrow(
|
|
SubtitleParseResponse,
|
|
await api.post<unknown>("/subtitles/parse", {
|
|
entityId,
|
|
files: await Promise.all(files.map(async (f) => ({ name: f.name, base64: await fileBase64(f) }))),
|
|
}),
|
|
"/subtitles/parse",
|
|
),
|
|
});
|
|
}
|
|
|
|
export function useApplySubtitles() {
|
|
const qc = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: async (body: SubtitleApplyRequest) =>
|
|
parseOrThrow(SubtitleApplyResult, await api.post<unknown>("/subtitles/apply", body), "/subtitles/apply"),
|
|
onSuccess: (_r, body) => {
|
|
qc.invalidateQueries({ queryKey: subtitleKeys.targets(body.entityId) });
|
|
qc.invalidateQueries({ queryKey: subtitleKeys.writes(body.entityId) });
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useUndoSubtitle(entityId: string) {
|
|
const qc = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: async (id: string) =>
|
|
parseOrThrow(
|
|
SubtitleWriteView,
|
|
await api.post<unknown>(`/subtitles/writes/${id}/undo`),
|
|
"/subtitles/writes/undo",
|
|
),
|
|
onSuccess: () => {
|
|
qc.invalidateQueries({ queryKey: subtitleKeys.targets(entityId) });
|
|
qc.invalidateQueries({ queryKey: subtitleKeys.writes(entityId) });
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useSearchSubtitles() {
|
|
return useMutation({
|
|
mutationFn: async (body: SubtitleSearchRequest) =>
|
|
parseOrThrow(SubtitleSearchResponse, await api.post<unknown>("/subtitles/search", body), "/subtitles/search"),
|
|
});
|
|
}
|
|
|
|
/** 下载一条在线字幕并解析(同上传的预览,不写文件) */
|
|
export function useFetchSubtitle() {
|
|
return useMutation({
|
|
mutationFn: async (body: SubtitleFetchRequest) =>
|
|
parseOrThrow(SubtitleParseResponse, await api.post<unknown>("/subtitles/fetch", body), "/subtitles/fetch"),
|
|
});
|
|
}
|