mirror of
https://github.com/p-stream/p-stream.git
synced 2026-01-11 20:10:32 +00:00
Add support for aborting and new lines
This commit is contained in:
parent
81f1272f7d
commit
b8a972f9ac
3 changed files with 116 additions and 37 deletions
|
|
@ -464,7 +464,7 @@ export const createSourceSlice: MakeSlice<SourceSlice> = (set, get) => ({
|
|||
return;
|
||||
}
|
||||
|
||||
let cancelled = false;
|
||||
const abortController = new AbortController();
|
||||
|
||||
set((s) => {
|
||||
s.caption.translateTask = {
|
||||
|
|
@ -476,16 +476,16 @@ export const createSourceSlice: MakeSlice<SourceSlice> = (set, get) => ({
|
|||
if (!this.done && !this.error) {
|
||||
console.log("Translation task was cancelled");
|
||||
}
|
||||
cancelled = true;
|
||||
abortController.abort();
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
function handleError(err: any) {
|
||||
console.error("Translation task ran into an error", err);
|
||||
if (cancelled) {
|
||||
if (abortController.signal.aborted) {
|
||||
return;
|
||||
}
|
||||
console.error("Translation task ran into an error", err);
|
||||
set((s) => {
|
||||
if (!s.caption.translateTask) return;
|
||||
s.caption.translateTask.error = true;
|
||||
|
|
@ -494,7 +494,7 @@ export const createSourceSlice: MakeSlice<SourceSlice> = (set, get) => ({
|
|||
|
||||
try {
|
||||
const srtData = await downloadCaption(targetCaption);
|
||||
if (cancelled) {
|
||||
if (abortController.signal.aborted) {
|
||||
return;
|
||||
}
|
||||
if (!srtData) {
|
||||
|
|
@ -519,8 +519,9 @@ export const createSourceSlice: MakeSlice<SourceSlice> = (set, get) => ({
|
|||
store.caption.translateTask!.fetchedTargetCaption!,
|
||||
targetLanguage,
|
||||
googletranslate,
|
||||
abortController.signal,
|
||||
);
|
||||
if (cancelled) {
|
||||
if (abortController.signal.aborted) {
|
||||
return;
|
||||
}
|
||||
if (!result) {
|
||||
|
|
|
|||
|
|
@ -12,23 +12,30 @@ export default {
|
|||
|
||||
getConfig() {
|
||||
return {
|
||||
singleBatchSize: 15,
|
||||
multiBatchSize: 80,
|
||||
single: {
|
||||
batchSize: 250,
|
||||
batchDelayMs: 1000,
|
||||
},
|
||||
multi: {
|
||||
batchSize: 80,
|
||||
batchDelayMs: 200,
|
||||
},
|
||||
maxRetryCount: 3,
|
||||
batchSleepMs: 200,
|
||||
};
|
||||
},
|
||||
|
||||
async translate(str, targetLang) {
|
||||
async translate(str, targetLang, abortSignal) {
|
||||
if (!str) {
|
||||
return "";
|
||||
}
|
||||
str = str.replaceAll("\n", "<br />");
|
||||
|
||||
const response = await (
|
||||
await fetch(
|
||||
`${SINGLE_API_URL}&tl=${targetLang}&q=${encodeURIComponent(str)}`,
|
||||
{
|
||||
method: "GET",
|
||||
signal: abortSignal,
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
},
|
||||
|
|
@ -43,17 +50,20 @@ export default {
|
|||
|
||||
return (response.sentences as any[])
|
||||
.map((s: any) => s.trans as string)
|
||||
.join("");
|
||||
.join("")
|
||||
.replaceAll("<br />", "\n");
|
||||
},
|
||||
|
||||
async translateMulti(batch, targetLang) {
|
||||
async translateMulti(batch, targetLang, abortSignal) {
|
||||
if (!batch || batch.length === 0) {
|
||||
return [];
|
||||
}
|
||||
batch = batch.map((s) => s.replaceAll("\n", "<br />"));
|
||||
|
||||
const response = await (
|
||||
await fetch(BATCH_API_URL, {
|
||||
method: "POST",
|
||||
signal: abortSignal,
|
||||
headers: {
|
||||
"Content-Type": "application/json+protobuf",
|
||||
"X-goog-api-key": BATCH_API_KEY,
|
||||
|
|
@ -67,6 +77,8 @@ export default {
|
|||
throw new Error("Invalid response");
|
||||
}
|
||||
|
||||
return response[0].map((s: any) => s as string);
|
||||
return response[0].map((s: any) =>
|
||||
(s as string).replaceAll("<br />", "\n"),
|
||||
);
|
||||
},
|
||||
} satisfies TranslateService;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable no-console */
|
||||
import subsrt from "subsrt-ts";
|
||||
import { Caption, ContentCaption } from "subsrt-ts/dist/types/handler";
|
||||
|
||||
|
|
@ -7,16 +8,32 @@ import { compressStr, decompressStr, sleep } from "./utils";
|
|||
|
||||
const CAPTIONS_CACHE: Map<string, ArrayBuffer> = new Map<string, ArrayBuffer>();
|
||||
|
||||
// single will not be used if multi-line is supported
|
||||
export interface TranslateServiceConfig {
|
||||
single: {
|
||||
batchSize: number;
|
||||
batchDelayMs: number;
|
||||
};
|
||||
multi?: {
|
||||
batchSize: number;
|
||||
batchDelayMs: number;
|
||||
};
|
||||
maxRetryCount: number;
|
||||
}
|
||||
|
||||
export interface TranslateService {
|
||||
getName(): string;
|
||||
getConfig(): {
|
||||
singleBatchSize: number;
|
||||
multiBatchSize: number; // -1 = unsupported
|
||||
maxRetryCount: number;
|
||||
batchSleepMs: number;
|
||||
};
|
||||
translate(str: string, targetLang: string): Promise<string>;
|
||||
translateMulti(batch: string[], targetLang: string): Promise<string[]>;
|
||||
getConfig(): TranslateServiceConfig;
|
||||
translate(
|
||||
str: string,
|
||||
targetLang: string,
|
||||
abortSignal?: AbortSignal,
|
||||
): Promise<string>;
|
||||
translateMulti(
|
||||
batch: string[],
|
||||
targetLang: string,
|
||||
abortSignal?: AbortSignal,
|
||||
): Promise<string[]>;
|
||||
}
|
||||
|
||||
class Translator {
|
||||
|
|
@ -30,10 +47,21 @@ class Translator {
|
|||
|
||||
private service: TranslateService;
|
||||
|
||||
constructor(srtData: string, targetLang: string, service: TranslateService) {
|
||||
private serviceCfg: TranslateServiceConfig;
|
||||
|
||||
private abortSignal?: AbortSignal;
|
||||
|
||||
constructor(
|
||||
srtData: string,
|
||||
targetLang: string,
|
||||
service: TranslateService,
|
||||
abortSignal?: AbortSignal,
|
||||
) {
|
||||
this.captions = subsrt.parse(srtData);
|
||||
this.targetLang = targetLang;
|
||||
this.service = service;
|
||||
this.serviceCfg = service.getConfig();
|
||||
this.abortSignal = abortSignal;
|
||||
|
||||
for (const caption of this.captions) {
|
||||
if (caption.type !== "caption") {
|
||||
|
|
@ -64,8 +92,15 @@ class Translator {
|
|||
|
||||
while (!result && attempts < 3) {
|
||||
try {
|
||||
result = await this.service.translate(content.text, this.targetLang);
|
||||
result = await this.service.translate(
|
||||
content.text,
|
||||
this.targetLang,
|
||||
this.abortSignal,
|
||||
);
|
||||
} catch (err) {
|
||||
if (this.abortSignal?.aborted) {
|
||||
break;
|
||||
}
|
||||
console.warn("Translation attempt failed");
|
||||
errors.push(err);
|
||||
await sleep(500);
|
||||
|
|
@ -73,6 +108,10 @@ class Translator {
|
|||
}
|
||||
}
|
||||
|
||||
if (this.abortSignal?.aborted) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
console.warn("Translation failed", errors);
|
||||
return false;
|
||||
|
|
@ -88,6 +127,7 @@ class Translator {
|
|||
const result = await this.service.translateMulti(
|
||||
batch.map((content) => content.text),
|
||||
this.targetLang,
|
||||
this.abortSignal,
|
||||
);
|
||||
|
||||
if (result.length !== batch.length) {
|
||||
|
|
@ -106,6 +146,9 @@ class Translator {
|
|||
|
||||
return true;
|
||||
} catch (err) {
|
||||
if (this.abortSignal?.aborted) {
|
||||
return false;
|
||||
}
|
||||
console.warn("Batch translation failed", err);
|
||||
return false;
|
||||
}
|
||||
|
|
@ -113,10 +156,9 @@ class Translator {
|
|||
|
||||
takeBatch(): ContentCaption[] {
|
||||
const batch: ContentCaption[] = [];
|
||||
const batchSize =
|
||||
this.service.getConfig().multiBatchSize === -1
|
||||
? this.service.getConfig().singleBatchSize
|
||||
: this.service.getConfig().multiBatchSize;
|
||||
const batchSize = !this.serviceCfg.multi
|
||||
? this.serviceCfg.single.batchSize
|
||||
: this.serviceCfg.multi!.batchSize;
|
||||
|
||||
let count = 0;
|
||||
while (count < batchSize && this.contentCaptions.length > 0) {
|
||||
|
|
@ -132,12 +174,24 @@ class Translator {
|
|||
}
|
||||
|
||||
async translate(): Promise<string | undefined> {
|
||||
const batchDelay = !this.serviceCfg.multi
|
||||
? this.serviceCfg.single.batchDelayMs
|
||||
: this.serviceCfg.multi!.batchDelayMs;
|
||||
|
||||
console.info(
|
||||
"Translating captions",
|
||||
this.service.getName(),
|
||||
this.contentCaptions.length,
|
||||
batchDelay,
|
||||
);
|
||||
console.time("translation");
|
||||
|
||||
let batch: ContentCaption[] = this.takeBatch();
|
||||
while (batch.length > 0) {
|
||||
let result: boolean;
|
||||
console.info("Translating captions batch", batch.length, batch);
|
||||
console.info("Translating batch", batch.length, batch);
|
||||
|
||||
if (this.service.getConfig().multiBatchSize === -1) {
|
||||
if (!this.serviceCfg.multi) {
|
||||
result = (
|
||||
await Promise.all(
|
||||
batch.map((content) => this.translateContent(content)),
|
||||
|
|
@ -147,18 +201,24 @@ class Translator {
|
|||
result = await this.translateContentBatch(batch);
|
||||
}
|
||||
|
||||
if (this.abortSignal?.aborted) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
console.error(
|
||||
"Failed to translate captions batch",
|
||||
batch.length,
|
||||
batch,
|
||||
);
|
||||
console.error("Failed to translate batch", batch.length, batch);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
batch = this.takeBatch();
|
||||
await sleep(this.service.getConfig().batchSleepMs);
|
||||
await sleep(batchDelay);
|
||||
}
|
||||
|
||||
if (this.abortSignal?.aborted) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
console.timeEnd("translation");
|
||||
return subsrt.build(this.captions, { format: "srt" });
|
||||
}
|
||||
}
|
||||
|
|
@ -167,6 +227,7 @@ export async function translate(
|
|||
caption: PlayerCaption,
|
||||
targetLang: string,
|
||||
service: TranslateService,
|
||||
abortSignal?: AbortSignal,
|
||||
): Promise<string | undefined> {
|
||||
const cacheID = `${caption.id}_${targetLang}`;
|
||||
|
||||
|
|
@ -175,10 +236,15 @@ export async function translate(
|
|||
return decompressStr(cachedData);
|
||||
}
|
||||
|
||||
const translator = new Translator(caption.srtData, targetLang, service);
|
||||
const translator = new Translator(
|
||||
caption.srtData,
|
||||
targetLang,
|
||||
service,
|
||||
abortSignal,
|
||||
);
|
||||
|
||||
const result = await translator.translate();
|
||||
if (!result) {
|
||||
if (!result || abortSignal?.aborted) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue