一些检查失败了
automerge / automerge (push) Has been cancelled
react-native-android-build-apk / AssembleRelease (push) Has been cancelled
Build macOS M1 / Main (push) Has been cancelled
Joplin Continuous Integration / Main (macos-15-intel) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-11-vs2026-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-2025) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04-arm) (push) Has been cancelled
Joplin UI tests / Main (ubuntu-22.04) (push) Has been cancelled
Joplin UI tests / Main (windows-2025) (push) Has been cancelled
Joplin UI tests / Mobile (push) Has been cancelled
Close stale issues / ProcessStaleIssues (push) Has been cancelled
136 行
3.8 KiB
TypeScript
136 行
3.8 KiB
TypeScript
import { emptyRecognizeResult, RecognizeResult } from '../utils/types';
|
|
import OcrDriverBase from '../OcrDriverBase';
|
|
import Logger from '@joplin/utils/Logger';
|
|
import { ResourceOcrDriverId, ResourceOcrStatus } from '../../database/types';
|
|
import KvStore from '../../KvStore';
|
|
import shim from '../../../shim';
|
|
import { msleep } from '@joplin/utils/time';
|
|
import Resource from '../../../models/Resource';
|
|
import { reg } from '../../../registry';
|
|
|
|
const logger = Logger.create('OcrDriverTranscribe');
|
|
|
|
type CreateJobResult = { jobId: string };
|
|
|
|
export default class OcrDriverTranscribe extends OcrDriverBase {
|
|
|
|
private retryIntervals_ = [10 * 1000, 15 * 1000, 30 * 1000, 60 * 1000];
|
|
private jobIdKeyPrefix_ = 'OcrDriverTranscribe::JobId::';
|
|
private disposed_ = false;
|
|
|
|
public constructor(interval?: number[]) {
|
|
super();
|
|
this.retryIntervals_ = interval ?? this.retryIntervals_;
|
|
}
|
|
|
|
public get driverId() {
|
|
return ResourceOcrDriverId.HandwrittenText;
|
|
}
|
|
|
|
public async recognize(_language: string, filePath: string, resourceId: string): Promise<RecognizeResult> {
|
|
logger.info(`${resourceId}: Starting to recognize resource from ${filePath}`);
|
|
|
|
const key = `${this.jobIdKeyPrefix_}${resourceId}`;
|
|
const storedJobId = await KvStore.instance().value(key);
|
|
let jobId = typeof storedJobId === 'string' ? storedJobId : null;
|
|
|
|
try {
|
|
if (!jobId) {
|
|
await Resource.save({
|
|
id: resourceId,
|
|
ocr_status: ResourceOcrStatus.Processing,
|
|
});
|
|
logger.info(`${resourceId}: Job does not exist yet, creating...`);
|
|
jobId = await this.queueJob(filePath, resourceId);
|
|
|
|
logger.info(`${resourceId}: Job created, reference: ${jobId}`);
|
|
await KvStore.instance().setValue(key, jobId);
|
|
}
|
|
|
|
const ocrResult = await this.checkJobIsFinished(jobId, resourceId);
|
|
await KvStore.instance().deleteValue(key);
|
|
|
|
return {
|
|
...emptyRecognizeResult(),
|
|
...ocrResult,
|
|
};
|
|
} catch (error) {
|
|
if (shim.fetchRequestCanBeRetried(error) || error.code === 503) {
|
|
return emptyRecognizeResult();
|
|
}
|
|
await KvStore.instance().deleteValue(key);
|
|
return {
|
|
...emptyRecognizeResult(),
|
|
ocr_status: ResourceOcrStatus.Error,
|
|
ocr_error: error.message,
|
|
};
|
|
}
|
|
}
|
|
|
|
private async queueJob(filePath: string, resourceId: string) {
|
|
const api = await this.api();
|
|
|
|
const result: CreateJobResult = await api.exec('POST', 'api/transcribe', null, null, {
|
|
'Content-Type': 'application/octet-stream',
|
|
}, { path: filePath, source: 'file' });
|
|
|
|
logger.info(`${resourceId}: Job queued`);
|
|
return result.jobId;
|
|
}
|
|
|
|
private async checkJobIsFinished(jobId: string, resourceId: string) {
|
|
logger.info(`${resourceId}: Checking if job is finished...`);
|
|
let i = 0;
|
|
while (true) {
|
|
if (this.disposed_) break;
|
|
|
|
const api = await this.api();
|
|
|
|
const response = await api.exec('GET', `api/transcribe/${jobId}`);
|
|
|
|
if (this.disposed_) break;
|
|
|
|
if (response.state === 'completed') {
|
|
logger.info(`${resourceId}: Finished.`);
|
|
return {
|
|
ocr_status: ResourceOcrStatus.Done,
|
|
ocr_text: response.output.result,
|
|
};
|
|
} else if (response.state === 'failed') {
|
|
logger.info(`${resourceId}: Failed.`);
|
|
return {
|
|
ocr_status: ResourceOcrStatus.Error,
|
|
ocr_error: response.output,
|
|
};
|
|
}
|
|
|
|
logger.info(`${resourceId}: Job not finished yet, waiting... ${this.getInterval(i)}`);
|
|
await msleep(this.getInterval(i));
|
|
i += 1;
|
|
}
|
|
|
|
return {
|
|
ocr_status: ResourceOcrStatus.Error,
|
|
ocr_error: 'OcrDriverTranscribe was stopped while waiting for a transcription',
|
|
};
|
|
}
|
|
|
|
private getInterval(index: number) {
|
|
if (index >= this.retryIntervals_.length) {
|
|
return this.retryIntervals_[this.retryIntervals_.length - 1];
|
|
}
|
|
return this.retryIntervals_[index];
|
|
}
|
|
|
|
private async api() {
|
|
const fileApi = await reg.syncTarget().fileApi();
|
|
return fileApi.driver().api();
|
|
}
|
|
|
|
public dispose() {
|
|
this.disposed_ = true;
|
|
return Promise.resolve();
|
|
}
|
|
|
|
}
|