dsh-convert-core 0.1.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +149 -0
  3. package/bin/media-to-md.mjs +168 -0
  4. package/bin/tita-convert.mjs +85 -0
  5. package/docs/media-to-md.md +143 -0
  6. package/lib/adapters/anydoc.d.ts +73 -0
  7. package/lib/adapters/anydoc.d.ts.map +1 -0
  8. package/lib/adapters/anydoc.js +295 -0
  9. package/lib/adapters/anydoc.js.map +1 -0
  10. package/lib/adapters/iddoc.d.ts +28 -0
  11. package/lib/adapters/iddoc.d.ts.map +1 -0
  12. package/lib/adapters/iddoc.js +153 -0
  13. package/lib/adapters/iddoc.js.map +1 -0
  14. package/lib/adapters/index.d.ts +41 -0
  15. package/lib/adapters/index.d.ts.map +1 -0
  16. package/lib/adapters/index.js +115 -0
  17. package/lib/adapters/index.js.map +1 -0
  18. package/lib/adapters/scanned.d.ts +28 -0
  19. package/lib/adapters/scanned.d.ts.map +1 -0
  20. package/lib/adapters/scanned.js +121 -0
  21. package/lib/adapters/scanned.js.map +1 -0
  22. package/lib/adapters/text.d.ts +29 -0
  23. package/lib/adapters/text.d.ts.map +1 -0
  24. package/lib/adapters/text.js +135 -0
  25. package/lib/adapters/text.js.map +1 -0
  26. package/lib/adapters/types.d.ts +65 -0
  27. package/lib/adapters/types.d.ts.map +1 -0
  28. package/lib/adapters/types.js +11 -0
  29. package/lib/adapters/types.js.map +1 -0
  30. package/lib/contract.d.ts +185 -0
  31. package/lib/contract.d.ts.map +1 -0
  32. package/lib/contract.js +59 -0
  33. package/lib/contract.js.map +1 -0
  34. package/lib/delivery.d.ts +36 -0
  35. package/lib/delivery.d.ts.map +1 -0
  36. package/lib/delivery.js +97 -0
  37. package/lib/delivery.js.map +1 -0
  38. package/lib/http.d.ts +34 -0
  39. package/lib/http.d.ts.map +1 -0
  40. package/lib/http.js +372 -0
  41. package/lib/http.js.map +1 -0
  42. package/lib/markdown.d.ts +32 -0
  43. package/lib/markdown.d.ts.map +1 -0
  44. package/lib/markdown.js +145 -0
  45. package/lib/markdown.js.map +1 -0
  46. package/lib/media/binaries.d.ts +26 -0
  47. package/lib/media/binaries.d.ts.map +1 -0
  48. package/lib/media/binaries.js +60 -0
  49. package/lib/media/binaries.js.map +1 -0
  50. package/lib/media/convert.d.ts +19 -0
  51. package/lib/media/convert.d.ts.map +1 -0
  52. package/lib/media/convert.js +210 -0
  53. package/lib/media/convert.js.map +1 -0
  54. package/lib/media/index.d.ts +27 -0
  55. package/lib/media/index.d.ts.map +1 -0
  56. package/lib/media/index.js +27 -0
  57. package/lib/media/index.js.map +1 -0
  58. package/lib/media/markdown.d.ts +40 -0
  59. package/lib/media/markdown.d.ts.map +1 -0
  60. package/lib/media/markdown.js +89 -0
  61. package/lib/media/markdown.js.map +1 -0
  62. package/lib/media/media.d.ts +45 -0
  63. package/lib/media/media.d.ts.map +1 -0
  64. package/lib/media/media.js +167 -0
  65. package/lib/media/media.js.map +1 -0
  66. package/lib/media/models.d.ts +31 -0
  67. package/lib/media/models.d.ts.map +1 -0
  68. package/lib/media/models.js +87 -0
  69. package/lib/media/models.js.map +1 -0
  70. package/lib/media/scanned.d.ts +18 -0
  71. package/lib/media/scanned.d.ts.map +1 -0
  72. package/lib/media/scanned.js +179 -0
  73. package/lib/media/scanned.js.map +1 -0
  74. package/lib/media/types.d.ts +154 -0
  75. package/lib/media/types.d.ts.map +1 -0
  76. package/lib/media/types.js +23 -0
  77. package/lib/media/types.js.map +1 -0
  78. package/lib/server.d.ts +35 -0
  79. package/lib/server.d.ts.map +1 -0
  80. package/lib/server.js +64 -0
  81. package/lib/server.js.map +1 -0
  82. package/lib/service.d.ts +170 -0
  83. package/lib/service.d.ts.map +1 -0
  84. package/lib/service.js +562 -0
  85. package/lib/service.js.map +1 -0
  86. package/package.json +59 -0
  87. package/scripts/asr.py +55 -0
  88. package/scripts/ocr.py +40 -0
  89. package/scripts/pdf_pages.py +49 -0
  90. package/scripts/vendor.mjs +76 -0
  91. package/src/adapters/anydoc.ts +356 -0
  92. package/src/adapters/iddoc.ts +171 -0
  93. package/src/adapters/index.ts +147 -0
  94. package/src/adapters/scanned.ts +139 -0
  95. package/src/adapters/text.ts +146 -0
  96. package/src/adapters/types.ts +76 -0
  97. package/src/contract.ts +230 -0
  98. package/src/delivery.ts +124 -0
  99. package/src/http.ts +394 -0
  100. package/src/markdown.ts +141 -0
  101. package/src/media/binaries.ts +67 -0
  102. package/src/media/convert.ts +232 -0
  103. package/src/media/index.ts +43 -0
  104. package/src/media/markdown.ts +105 -0
  105. package/src/media/media.ts +204 -0
  106. package/src/media/models.ts +124 -0
  107. package/src/media/scanned.ts +200 -0
  108. package/src/media/types.ts +171 -0
  109. package/src/server.ts +85 -0
  110. package/src/service.ts +639 -0
  111. package/web/app.js +382 -0
  112. package/web/index.html +217 -0
  113. package/web/styles.css +263 -0
  114. package/web/vendor/icons.js +25 -0
  115. package/web/vendor/vue.global.prod.js +14 -0
@@ -0,0 +1,154 @@
1
+ /**
2
+ * media-to-md 的公开契约。
3
+ *
4
+ * 这个包只做一件事:把一个**本地**音视频/图片文件变成「知识就绪 Markdown + 本地图片」。
5
+ * 它刻意不认识任何宿主(转换内核、CLI、未来的桌面端),宿主拿到的就是一个纯函数:
6
+ *
7
+ * convertMedia(options) → { markdown, assets[], meta, transcript, warnings, … }
8
+ *
9
+ * 全程本地:不调用云端 API、不调用大模型。用到的两个**专用小模型**(语音识别 whisper、
10
+ * 画面文字 OCR)都是可选档位,且是否允许下载由调用方显式给出(`allowModelDownload`)。
11
+ */
12
+ export type MediaSourceFormat = 'video' | 'audio' | 'image' | 'pdf';
13
+ /** 解析档位。`auto` = 有字幕用字幕,没有才考虑转写(且必须允许下载模型)。 */
14
+ export type MediaMode = 'auto' | 'subtitle-only' | 'transcribe' | 'frames-only';
15
+ export interface MediaAsrOptions {
16
+ /** whisper 模型名(tiny/base/small/medium/large-v3/large-v3-turbo)。 */
17
+ model?: string;
18
+ /** 语言代码(zh/en…);省略则自动检测。 */
19
+ language?: string;
20
+ /** faster-whisper 设备与量化,默认 cpu/int8。 */
21
+ device?: string;
22
+ computeType?: string;
23
+ beamSize?: number;
24
+ /**
25
+ * 是否允许下载模型。默认 **false**:
26
+ * 无字幕又没有本地模型时会抛 `ASR_MODEL_REQUIRED`,把「要下多大」摊在调用方面前。
27
+ */
28
+ allowModelDownload?: boolean;
29
+ }
30
+ export interface MediaFrameOptions {
31
+ /** 均匀采样帧数(保证任何视频都有基础覆盖)。 */
32
+ samples?: number;
33
+ /** 关键帧上限。 */
34
+ maxFrames?: number;
35
+ /** 场景切换阈值(0–1);硬切/翻页会额外抽帧。 */
36
+ sceneThreshold?: number;
37
+ }
38
+ export interface MediaToolOptions {
39
+ /** uvx 可执行文件;默认 `uvx`。 */
40
+ uvxPath?: string;
41
+ /** uvx 使用的 Python 版本;默认 `3.12`。 */
42
+ pythonVersion?: string;
43
+ }
44
+ export interface MediaToMarkdownOptions {
45
+ /** 本地文件绝对路径。 */
46
+ file: string;
47
+ /** 产物标题;默认取文件名(去扩展名)。 */
48
+ title?: string;
49
+ mode?: MediaMode;
50
+ asr?: MediaAsrOptions;
51
+ frames?: MediaFrameOptions;
52
+ tools?: MediaToolOptions;
53
+ /** 对**图片**输入开启本地 OCR;默认 false(issue 0013:图片直接收录、靠多模态召回)。 */
54
+ imageOcr?: boolean;
55
+ /** 对视频**关键帧**做画面文字 OCR;默认 false。 */
56
+ ocrFrames?: boolean;
57
+ /** 保留中间目录(排障用)。 */
58
+ keepScratch?: boolean;
59
+ /** 取消信号。 */
60
+ signal?: AbortSignal;
61
+ onProgress?: (progress: MediaToMarkdownProgress) => void;
62
+ }
63
+ /**
64
+ * 轻量扫描档:纯图片页 PDF → 逐页本地 OCR。
65
+ *
66
+ * 刻意**不引入 docling / PaddlePaddle 框架**:它只解决"扫描件里的字读不出来",
67
+ * 版面顺序与表格结构不在这一档的承诺范围内(见 issues/0017 的可选高质量档)。
68
+ */
69
+ export interface ScannedPdfOptions {
70
+ file: string;
71
+ title?: string;
72
+ /** 最多渲染并识别的页数(默认 50);超出会写 warning,不静默截断。 */
73
+ maxPages?: number;
74
+ /** 渲染 DPI(默认 150);越高越准,也越慢越大。 */
75
+ dpi?: number;
76
+ /** 是否把逐页图片作为 assets 落地(默认 true;false 时只保留文字)。 */
77
+ pageImages?: boolean;
78
+ tools?: MediaToolOptions;
79
+ keepScratch?: boolean;
80
+ signal?: AbortSignal;
81
+ onProgress?: (progress: MediaToMarkdownProgress) => void;
82
+ }
83
+ /** 扫描档的页级结果。 */
84
+ export interface ScannedPage {
85
+ index: number;
86
+ /** 该页图片在产物里的相对路径(未落地图片时为 `page-NNNN.png` 的约定名)。 */
87
+ rel: string;
88
+ width?: number;
89
+ height?: number;
90
+ text: string;
91
+ ocrLines: number;
92
+ }
93
+ export interface MediaToMarkdownProgress {
94
+ stage: 'probing' | 'frames' | 'subtitles' | 'transcribing' | 'ocr' | 'assembling';
95
+ /** 0–1,未知时为 null。 */
96
+ progress: number | null;
97
+ message?: string;
98
+ }
99
+ export interface MediaAsset {
100
+ /** 相对产物根的路径,**含 `assets/` 前缀**,可直接写进 Markdown。 */
101
+ rel: string;
102
+ data: Buffer;
103
+ contentType?: string;
104
+ }
105
+ export interface MediaCue {
106
+ seconds: number;
107
+ /** 结束时间(字幕/SRT 有;自动转写也有)。缺失时按下一条的开始时间处理。 */
108
+ endSeconds?: number;
109
+ text: string;
110
+ }
111
+ export interface MediaMeta {
112
+ sourceFormat: MediaSourceFormat;
113
+ /** 容器/图片扩展名(不含点),例如 `mp4`、`png` —— 宿主用它拼 parser 标识。 */
114
+ containerExtension?: string;
115
+ durationSeconds: number;
116
+ sizeBytes: number;
117
+ width?: number;
118
+ height?: number;
119
+ fps?: number;
120
+ videoCodec?: string;
121
+ audioCodec?: string;
122
+ hasVideo: boolean;
123
+ hasAudio: boolean;
124
+ hasSubtitleTrack: boolean;
125
+ /** 实际用到的字幕来源。 */
126
+ transcriptSource: 'subtitle' | 'transcription' | 'none';
127
+ }
128
+ export interface MediaWarning {
129
+ code: string;
130
+ message: string;
131
+ }
132
+ export interface MediaToMarkdownResult {
133
+ title: string;
134
+ /** Markdown 正文;**不含 front matter**(front matter 是宿主的契约)。 */
135
+ markdown: string;
136
+ assets: MediaAsset[];
137
+ meta: MediaMeta;
138
+ transcript: {
139
+ source: MediaMeta['transcriptSource'];
140
+ cues: MediaCue[];
141
+ };
142
+ warnings: MediaWarning[];
143
+ dependencyVersions: Record<string, string>;
144
+ /** 文件内容 hash(sha256:…),给宿主做幂等。 */
145
+ externalId: string;
146
+ /** 扫描档才有:逐页文本与图片。 */
147
+ pages?: ScannedPage[];
148
+ }
149
+ /** 带可操作建议的错误:宿主(内核/CLI)应当原样展示 `code` 与 `suggestion`。 */ export declare class MediaToMarkdownError extends Error {
150
+ readonly code: string;
151
+ readonly suggestion?: string;
152
+ constructor(code: string, message: string, suggestion?: string);
153
+ }
154
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/media/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * media-to-md 的公开契约。
3
+ *
4
+ * 这个包只做一件事:把一个**本地**音视频/图片文件变成「知识就绪 Markdown + 本地图片」。
5
+ * 它刻意不认识任何宿主(转换内核、CLI、未来的桌面端),宿主拿到的就是一个纯函数:
6
+ *
7
+ * convertMedia(options) → { markdown, assets[], meta, transcript, warnings, … }
8
+ *
9
+ * 全程本地:不调用云端 API、不调用大模型。用到的两个**专用小模型**(语音识别 whisper、
10
+ * 画面文字 OCR)都是可选档位,且是否允许下载由调用方显式给出(`allowModelDownload`)。
11
+ */
12
+ /** 带可操作建议的错误:宿主(内核/CLI)应当原样展示 `code` 与 `suggestion`。 */ export class MediaToMarkdownError extends Error {
13
+ code;
14
+ suggestion;
15
+ constructor(code, message, suggestion) {
16
+ super(message);
17
+ this.name = 'MediaToMarkdownError';
18
+ this.code = code;
19
+ if (suggestion !== undefined)
20
+ this.suggestion = suggestion;
21
+ }
22
+ }
23
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/media/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAsJH,wDAAwD,CAAA,MAAM,OAAO,oBAAqB,SAAQ,KAAK;IAC5F,IAAI,CAAQ;IACZ,UAAU,CAAS;IAE5B,YAAY,IAAY,EAAE,OAAe,EAAE,UAAmB;QAC5D,KAAK,CAAC,OAAO,CAAC,CAAA;QACd,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAA;QAClC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;QAChB,IAAI,UAAU,KAAK,SAAS;YAAE,IAAI,CAAC,UAAU,GAAG,UAAU,CAAA;IAC5D,CAAC;CACF"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Public entry point of the conversion kernel.
3
+ *
4
+ * Two integration shapes, one implementation:
5
+ * const service = await createConvertService({ rootDir })
6
+ * const server = await startConvertServer({ port: 8787 }) // standalone
7
+ * const handler = createRequestHandler({ service, basePath }) // embedded
8
+ */
9
+ import { type Server } from 'node:http';
10
+ import { ConvertService, type ConvertServiceOptions } from './service.js';
11
+ export * from './contract.js';
12
+ export * from './service.js';
13
+ export * from './delivery.js';
14
+ export * from './adapters/index.js';
15
+ export * from './adapters/types.js';
16
+ export { createRequestHandler, type HandlerOptions } from './http.js';
17
+ export interface CreateServiceOptions extends ConvertServiceOptions {
18
+ }
19
+ export declare function createConvertService(options?: CreateServiceOptions): Promise<ConvertService>;
20
+ export interface StartServerOptions extends CreateServiceOptions {
21
+ port?: number;
22
+ host?: string;
23
+ basePath?: string;
24
+ webRoot?: string;
25
+ apiKey?: string;
26
+ }
27
+ export interface RunningConvertServer {
28
+ service: ConvertService;
29
+ server: Server;
30
+ url: string;
31
+ close: () => Promise<void>;
32
+ }
33
+ /** Start the kernel standalone — the console is at `url`, the API under `url/api`. */
34
+ export declare function startConvertServer(options?: StartServerOptions): Promise<RunningConvertServer>;
35
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAgB,KAAK,MAAM,EAAE,MAAM,WAAW,CAAA;AAErD,OAAO,EAAE,cAAc,EAAsB,KAAK,qBAAqB,EAAE,MAAM,cAAc,CAAA;AAE7F,cAAc,eAAe,CAAA;AAC7B,cAAc,cAAc,CAAA;AAC5B,cAAc,eAAe,CAAA;AAC7B,cAAc,qBAAqB,CAAA;AACnC,cAAc,qBAAqB,CAAA;AACnC,OAAO,EAAE,oBAAoB,EAAE,KAAK,cAAc,EAAE,MAAM,WAAW,CAAA;AAErE,MAAM,WAAW,oBAAqB,SAAQ,qBAAqB;CAAG;AAEtE,wBAAsB,oBAAoB,CAAC,OAAO,GAAE,oBAAyB,GAAG,OAAO,CAAC,cAAc,CAAC,CAItG;AAED,MAAM,WAAW,kBAAmB,SAAQ,oBAAoB;IAC9D,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,cAAc,CAAA;IACvB,MAAM,EAAE,MAAM,CAAA;IACd,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAC3B;AAED,sFAAsF;AACtF,wBAAsB,kBAAkB,CAAC,OAAO,GAAE,kBAAuB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAwCxG"}
package/lib/server.js ADDED
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Public entry point of the conversion kernel.
3
+ *
4
+ * Two integration shapes, one implementation:
5
+ * const service = await createConvertService({ rootDir })
6
+ * const server = await startConvertServer({ port: 8787 }) // standalone
7
+ * const handler = createRequestHandler({ service, basePath }) // embedded
8
+ */
9
+ import { createServer } from 'node:http';
10
+ import { createRequestHandler } from './http.js';
11
+ import { ConvertService, defaultConvertRoot } from './service.js';
12
+ export * from './contract.js';
13
+ export * from './service.js';
14
+ export * from './delivery.js';
15
+ export * from './adapters/index.js';
16
+ export * from './adapters/types.js';
17
+ export { createRequestHandler } from './http.js';
18
+ export async function createConvertService(options = {}) {
19
+ const service = new ConvertService({ rootDir: options.rootDir ?? defaultConvertRoot(), ...options });
20
+ await service.start();
21
+ return service;
22
+ }
23
+ /** Start the kernel standalone — the console is at `url`, the API under `url/api`. */
24
+ export async function startConvertServer(options = {}) {
25
+ const service = await createConvertService(options);
26
+ const handlerOptions = {
27
+ service,
28
+ basePath: options.basePath ?? '',
29
+ ...(options.webRoot ? { webRoot: options.webRoot } : {}),
30
+ ...(options.apiKey ? { apiKey: options.apiKey } : {}),
31
+ };
32
+ const handler = createRequestHandler(handlerOptions);
33
+ const server = createServer((req, res) => {
34
+ void handler(req, res).catch(() => {
35
+ if (!res.headersSent) {
36
+ res.statusCode = 500;
37
+ res.setHeader('content-type', 'application/json; charset=utf-8');
38
+ }
39
+ res.end(JSON.stringify({ ok: false, error: { code: 'INTERNAL_ERROR', message: '未捕获的服务端错误' } }));
40
+ });
41
+ });
42
+ const port = options.port ?? 8787;
43
+ const host = options.host ?? '127.0.0.1';
44
+ await new Promise((resolve, reject) => {
45
+ server.once('error', reject);
46
+ server.listen(port, host, () => {
47
+ server.off('error', reject);
48
+ resolve();
49
+ });
50
+ });
51
+ const address = server.address();
52
+ const actualPort = typeof address === 'object' && address ? address.port : port;
53
+ const basePath = (options.basePath ?? '').replace(/\/+$/, '');
54
+ return {
55
+ service,
56
+ server,
57
+ url: `http://${host === '0.0.0.0' ? '127.0.0.1' : host}:${actualPort}${basePath}`,
58
+ close: async () => {
59
+ await service.close();
60
+ await new Promise(resolve => server.close(() => resolve()));
61
+ },
62
+ };
63
+ }
64
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,170 @@
1
+ /**
2
+ * The conversion service: queue, lifecycle, delivery, observability.
3
+ *
4
+ * It owns no transport. `http.ts` mounts it on `node:http`, the DSH plugin
5
+ * mounts the same handler on the host web server, and `bin/tita-convert.mjs`
6
+ * runs it standalone — the "kernel first, wrapper second" split.
7
+ */
8
+ import { type CapabilityReport } from './adapters/index.js';
9
+ import type { ConvertRequest, ConvertTask, IngestPayload, TaskState } from './contract.js';
10
+ export interface ConvertServiceOptions {
11
+ /** Root for task state (`tasks.json`). Defaults to `~/.tita-data-agent`. */
12
+ rootDir?: string;
13
+ /**
14
+ * Root the **deliveries** are written under. Defaults to `<rootDir>/convert-out`.
15
+ *
16
+ * 与 `bucketByDay` 配合决定形状:不分桶是 `<outRoot>/<taskId>/`,分桶是
17
+ * `<outRoot>/<YYYY-MM-DD>/<taskId>/`。任务目录始终是原子的交付单位。
18
+ */
19
+ outRoot?: string;
20
+ /**
21
+ * Root the per-task **working** directories live under. Defaults to
22
+ * `<rootDir>/work`. 形状与 `outRoot` 一致。
23
+ */
24
+ workRoot?: string;
25
+ /**
26
+ * 交付与工作区是否按日期分桶(0029)。
27
+ *
28
+ * **默认 false,是刻意的**:这是内核,独立运行(`bin/tita-convert.mjs`)与既有
29
+ * 调用方都不该因为插件侧的目录结构调整而换位置。TitaAgent 插件显式传 `true`,
30
+ * 因为它的根路径契约里 `output/` 与 `tmp/` 的二级就是日期。
31
+ *
32
+ * 分桶的理由:这两处是「按时间堆积」的东西,不分桶的话一个月后就是一个几百条
33
+ * 平铺的目录,`ls` 出来读不出「上周那份报表在哪」。
34
+ */
35
+ bucketByDay?: boolean;
36
+ maxConcurrent?: number;
37
+ maxInputBytes?: number;
38
+ /** Keep at most this many finished tasks in the store. */
39
+ maxTasks?: number;
40
+ /** Delete deliveries older than this on start. 0 disables cleanup. */
41
+ retentionDays?: number;
42
+ /**
43
+ * How long a capability report stays valid. Defaults to
44
+ * {@link DEFAULT_CAPABILITY_TTL_MS}; 0 disables caching.
45
+ *
46
+ * See {@link ConvertService.capabilities} for why this exists: building the
47
+ * report spawns external binaries, and the console polls it on a timer.
48
+ */
49
+ capabilityTtlMs?: number;
50
+ }
51
+ /**
52
+ * Default lifetime of a capability report: 30 seconds.
53
+ *
54
+ * Long enough that a polling panel costs one probe per half minute instead of one
55
+ * per poll; short enough that installing ffmpeg and reopening the panel shows it
56
+ * without restarting anything.
57
+ */
58
+ export declare const DEFAULT_CAPABILITY_TTL_MS = 30000;
59
+ export interface SubmitInput {
60
+ filename?: string;
61
+ /** Raw bytes for file / video uploads. */
62
+ data?: Buffer;
63
+ }
64
+ export type ConvertListener = (task: ConvertTask) => void;
65
+ export declare const DEFAULT_ROOT: string;
66
+ export declare function defaultConvertRoot(): string;
67
+ export declare class ConvertService {
68
+ readonly rootDir: string;
69
+ /** 交付物根。每个任务的具体交付目录见 `deliveryDirOf()`。 */
70
+ readonly outRoot: string;
71
+ /** 工作区根。每个任务的具体工作目录见 `workDirOf()`。 */
72
+ readonly workRoot: string;
73
+ /** 交付与工作区是否按日期分桶。见 `ConvertServiceOptions.bucketByDay`。 */
74
+ readonly bucketByDay: boolean;
75
+ private readonly options;
76
+ private readonly tasks;
77
+ private readonly controllers;
78
+ private readonly listeners;
79
+ private readonly queue;
80
+ private running;
81
+ private closed;
82
+ private persistTimer;
83
+ private persistTail;
84
+ /** Last capability report and when it was built. See {@link capabilities}. */
85
+ private capabilityCache;
86
+ constructor(options?: ConvertServiceOptions);
87
+ /**
88
+ * @deprecated 0029:`outDir` 现在是 `outRoot` 的别名。保留这个名字只为兼容
89
+ * 既有的能力报告(`host.ts` 会回显它);新代码用 `outRoot` + `deliveryDirOf()`。
90
+ */
91
+ get outDir(): string;
92
+ /** @deprecated 0029:同上,用 `workRoot` + `workDirOf()`。 */
93
+ get workDir(): string;
94
+ /**
95
+ * 一个任务的交付目录。
96
+ *
97
+ * 分桶时 `<outRoot>/<日期>/<taskId>`,否则 `<outRoot>/<taskId>`。**按任务的
98
+ * `createdAt` 算而不是按「现在」**:重试一个昨天的任务不该把它换到今天来,
99
+ * 否则同一个交付会因为一次重试而换位置。
100
+ */
101
+ deliveryDirOf(task: Pick<ConvertTask, 'id' | 'createdAt'>): string;
102
+ /** 一个任务的工作目录。形状与交付目录一致。 */
103
+ workDirOf(task: Pick<ConvertTask, 'id' | 'createdAt'>): string;
104
+ private taskDir;
105
+ start(): Promise<void>;
106
+ close(): Promise<void>;
107
+ subscribe(listener: ConvertListener): () => void;
108
+ private emit;
109
+ get(id: string): ConvertTask | undefined;
110
+ list(): ConvertTask[];
111
+ /**
112
+ * Report which adapters can run and which external binaries exist.
113
+ *
114
+ * **Cached, because building this is not free.** `capabilityReport()` probes
115
+ * every adapter, and the media adapter's probe runs `ffprobe -version`,
116
+ * `ffmpeg -version` and `uvx --version` — three child processes per call, which
117
+ * measured ~60ms of server time. The conversion console polls this endpoint
118
+ * every four seconds while its tab is visible, so an uncached report cost about
119
+ * 45 processes and a second of CPU per minute. That lands on the same
120
+ * single-threaded process that serves the GUI's HTTP and event stream, which is
121
+ * how a status panel becomes everyone's latency.
122
+ *
123
+ * Nothing it reports changes second to second: an installed binary stays
124
+ * installed. {@link ConvertServiceOptions.capabilityTtlMs} bounds the staleness.
125
+ *
126
+ * @param options - `force` bypasses the cache, for a caller that just changed
127
+ * the environment (or a test that must not read another test's report).
128
+ * @returns the capability report.
129
+ */
130
+ capabilities(options?: {
131
+ force?: boolean;
132
+ }): Promise<CapabilityReport>;
133
+ submit(request: ConvertRequest, input?: SubmitInput): Promise<ConvertTask>;
134
+ cancel(id: string): ConvertTask;
135
+ retry(id: string): Promise<ConvertTask>;
136
+ private patch;
137
+ private pump;
138
+ private execute;
139
+ /** Resolve a path inside a delivery directory, refusing traversal. */
140
+ resolveDeliveryFile(id: string, relative: string): string | undefined;
141
+ readDeliveryFile(id: string, relative: string): Promise<{
142
+ data: Buffer;
143
+ contentType: string;
144
+ } | undefined>;
145
+ ingestPlan(id: string): Promise<IngestPayload>;
146
+ listDeliveries(): Array<{
147
+ taskId: string;
148
+ title: string;
149
+ deliveryDir: string;
150
+ state: TaskState;
151
+ assets: number;
152
+ }>;
153
+ private schedulePersist;
154
+ private persist;
155
+ private load;
156
+ /**
157
+ * 按保留期清理交付物。
158
+ *
159
+ * `outRoot` 下面那一层**要么是日期桶、要么是任务目录**,取决于 `bucketByDay`;
160
+ * 两种情况下它都是「一个可整体丢弃的单位」,所以清理规则只有一条:这一层的
161
+ * mtime 早于截止就整块删。分桶时不必再往下一层逐个判 —— 桶内任务通常是同一天
162
+ * 建的,逐个判既白判又会留下一个空壳日期目录。
163
+ */
164
+ private cleanup;
165
+ }
166
+ export declare function safeFilename(value: string): string;
167
+ /** Slug used when the DSH layer files a delivery as a knowledge source. */
168
+ export declare function knowledgeSlugFor(taskId: string, title: string): string;
169
+ export declare function contentTypeOf(path: string): string;
170
+ //# sourceMappingURL=service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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"}