@tienne/gestalt 0.102.0 → 0.104.0
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.
- package/README.ko.md +2 -1
- package/README.md +2 -1
- package/dist/package.json +1 -1
- package/dist/plugin/role-agents/video-summarizer/AGENT.md +208 -108
- package/dist/plugin/skills/_shared/proactive-routing.md +2 -1
- package/dist/plugin/skills/research/references/design.md +1 -1
- package/dist/plugin/skills/video-digest/SKILL.md +430 -0
- package/dist/plugin/skills/video-digest/templates/background.md +15 -0
- package/dist/plugin/skills/video-digest/templates/blog.md +16 -0
- package/dist/plugin/skills/video-digest/templates/comments.md +19 -0
- package/dist/plugin/skills/video-digest/templates/counterpoints.md +15 -0
- package/dist/plugin/skills/video-digest/templates/flashcards.md +15 -0
- package/dist/plugin/skills/video-digest/templates/interview.md +15 -0
- package/dist/plugin/skills/video-digest/templates/meeting.md +15 -0
- package/dist/plugin/skills/video-digest/templates/mindmap.md +16 -0
- package/dist/plugin/skills/video-digest/templates/quiz.md +16 -0
- package/dist/plugin/skills/video-digest/templates/quotes.md +16 -0
- package/dist/plugin/skills/video-digest/templates/resources.md +14 -0
- package/dist/plugin/skills/video-digest/templates/timestamps.md +14 -0
- package/dist/plugin/skills/video-digest/templates/visual.md +22 -0
- package/dist/src/cli/commands/digest.d.ts +60 -0
- package/dist/src/cli/commands/digest.d.ts.map +1 -0
- package/dist/src/cli/commands/digest.js +401 -0
- package/dist/src/cli/commands/digest.js.map +1 -0
- package/dist/src/cli/index.d.ts.map +1 -1
- package/dist/src/cli/index.js +81 -0
- package/dist/src/cli/index.js.map +1 -1
- package/dist/src/digest/blocks.d.ts +5224 -0
- package/dist/src/digest/blocks.d.ts.map +1 -0
- package/dist/src/digest/blocks.js +156 -0
- package/dist/src/digest/blocks.js.map +1 -0
- package/dist/src/digest/captions.d.ts +37 -0
- package/dist/src/digest/captions.d.ts.map +1 -0
- package/dist/src/digest/captions.js +138 -0
- package/dist/src/digest/captions.js.map +1 -0
- package/dist/src/digest/comments.d.ts +113 -0
- package/dist/src/digest/comments.d.ts.map +1 -0
- package/dist/src/digest/comments.js +153 -0
- package/dist/src/digest/comments.js.map +1 -0
- package/dist/src/digest/diagram-schema.d.ts +765 -0
- package/dist/src/digest/diagram-schema.d.ts.map +1 -0
- package/dist/src/digest/diagram-schema.js +72 -0
- package/dist/src/digest/diagram-schema.js.map +1 -0
- package/dist/src/digest/format.d.ts +9 -0
- package/dist/src/digest/format.d.ts.map +1 -0
- package/dist/src/digest/format.js +26 -0
- package/dist/src/digest/format.js.map +1 -0
- package/dist/src/digest/frames.d.ts +46 -0
- package/dist/src/digest/frames.d.ts.map +1 -0
- package/dist/src/digest/frames.js +206 -0
- package/dist/src/digest/frames.js.map +1 -0
- package/dist/src/digest/index.d.ts +26 -0
- package/dist/src/digest/index.d.ts.map +1 -0
- package/dist/src/digest/index.js +26 -0
- package/dist/src/digest/index.js.map +1 -0
- package/dist/src/digest/ingest.d.ts +65 -0
- package/dist/src/digest/ingest.d.ts.map +1 -0
- package/dist/src/digest/ingest.js +303 -0
- package/dist/src/digest/ingest.js.map +1 -0
- package/dist/src/digest/lint-extension.d.ts +71 -0
- package/dist/src/digest/lint-extension.d.ts.map +1 -0
- package/dist/src/digest/lint-extension.js +512 -0
- package/dist/src/digest/lint-extension.js.map +1 -0
- package/dist/src/digest/lint.d.ts +50 -0
- package/dist/src/digest/lint.d.ts.map +1 -0
- package/dist/src/digest/lint.js +560 -0
- package/dist/src/digest/lint.js.map +1 -0
- package/dist/src/digest/merge.d.ts +40 -0
- package/dist/src/digest/merge.d.ts.map +1 -0
- package/dist/src/digest/merge.js +107 -0
- package/dist/src/digest/merge.js.map +1 -0
- package/dist/src/digest/ocr.d.ts +100 -0
- package/dist/src/digest/ocr.d.ts.map +1 -0
- package/dist/src/digest/ocr.js +179 -0
- package/dist/src/digest/ocr.js.map +1 -0
- package/dist/src/digest/prose.d.ts +47 -0
- package/dist/src/digest/prose.d.ts.map +1 -0
- package/dist/src/digest/prose.js +171 -0
- package/dist/src/digest/prose.js.map +1 -0
- package/dist/src/digest/render-blocks.d.ts +38 -0
- package/dist/src/digest/render-blocks.d.ts.map +1 -0
- package/dist/src/digest/render-blocks.js +378 -0
- package/dist/src/digest/render-blocks.js.map +1 -0
- package/dist/src/digest/render-html.d.ts +22 -0
- package/dist/src/digest/render-html.d.ts.map +1 -0
- package/dist/src/digest/render-html.js +505 -0
- package/dist/src/digest/render-html.js.map +1 -0
- package/dist/src/digest/render-md.d.ts +18 -0
- package/dist/src/digest/render-md.d.ts.map +1 -0
- package/dist/src/digest/render-md.js +126 -0
- package/dist/src/digest/render-md.js.map +1 -0
- package/dist/src/digest/render.d.ts +47 -0
- package/dist/src/digest/render.d.ts.map +1 -0
- package/dist/src/digest/render.js +126 -0
- package/dist/src/digest/render.js.map +1 -0
- package/dist/src/digest/reports.d.ts +26 -0
- package/dist/src/digest/reports.d.ts.map +1 -0
- package/dist/src/digest/reports.js +70 -0
- package/dist/src/digest/reports.js.map +1 -0
- package/dist/src/digest/scenes.d.ts +92 -0
- package/dist/src/digest/scenes.d.ts.map +1 -0
- package/dist/src/digest/scenes.js +182 -0
- package/dist/src/digest/scenes.js.map +1 -0
- package/dist/src/digest/schema-base.d.ts +31 -0
- package/dist/src/digest/schema-base.d.ts.map +1 -0
- package/dist/src/digest/schema-base.js +19 -0
- package/dist/src/digest/schema-base.js.map +1 -0
- package/dist/src/digest/schema.d.ts +12886 -0
- package/dist/src/digest/schema.d.ts.map +1 -0
- package/dist/src/digest/schema.js +215 -0
- package/dist/src/digest/schema.js.map +1 -0
- package/dist/src/digest/server.d.ts +36 -0
- package/dist/src/digest/server.d.ts.map +1 -0
- package/dist/src/digest/server.js +278 -0
- package/dist/src/digest/server.js.map +1 -0
- package/dist/src/digest/slice.d.ts +20 -0
- package/dist/src/digest/slice.d.ts.map +1 -0
- package/dist/src/digest/slice.js +48 -0
- package/dist/src/digest/slice.js.map +1 -0
- package/dist/src/digest/store.d.ts +67 -0
- package/dist/src/digest/store.d.ts.map +1 -0
- package/dist/src/digest/store.js +152 -0
- package/dist/src/digest/store.js.map +1 -0
- package/dist/src/digest/svg.d.ts +74 -0
- package/dist/src/digest/svg.d.ts.map +1 -0
- package/dist/src/digest/svg.js +415 -0
- package/dist/src/digest/svg.js.map +1 -0
- package/dist/src/digest/templates.d.ts +38 -0
- package/dist/src/digest/templates.d.ts.map +1 -0
- package/dist/src/digest/templates.js +125 -0
- package/dist/src/digest/templates.js.map +1 -0
- package/dist/src/digest/translate.d.ts +46 -0
- package/dist/src/digest/translate.d.ts.map +1 -0
- package/dist/src/digest/translate.js +50 -0
- package/dist/src/digest/translate.js.map +1 -0
- package/dist/src/digest/viewer-js.d.ts +4 -0
- package/dist/src/digest/viewer-js.d.ts.map +1 -0
- package/dist/src/digest/viewer-js.js +735 -0
- package/dist/src/digest/viewer-js.js.map +1 -0
- package/dist/src/digest/viewer-logic.d.ts +46 -0
- package/dist/src/digest/viewer-logic.d.ts.map +1 -0
- package/dist/src/digest/viewer-logic.js +89 -0
- package/dist/src/digest/viewer-logic.js.map +1 -0
- package/dist/src/digest/ytdlp.d.ts +34 -0
- package/dist/src/digest/ytdlp.d.ts.map +1 -0
- package/dist/src/digest/ytdlp.js +64 -0
- package/dist/src/digest/ytdlp.js.map +1 -0
- package/dist/src/mcp/schemas.d.ts +48 -0
- package/dist/src/mcp/schemas.d.ts.map +1 -1
- package/dist/src/mcp/schemas.js +25 -0
- package/dist/src/mcp/schemas.js.map +1 -1
- package/dist/src/mcp/server.d.ts.map +1 -1
- package/dist/src/mcp/server.js +7 -1
- package/dist/src/mcp/server.js.map +1 -1
- package/dist/src/mcp/tools/digest-passthrough.d.ts +3 -0
- package/dist/src/mcp/tools/digest-passthrough.d.ts.map +1 -0
- package/dist/src/mcp/tools/digest-passthrough.js +151 -0
- package/dist/src/mcp/tools/digest-passthrough.js.map +1 -0
- package/package.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/.mcp.json +1 -1
- package/plugin/mcp.json +1 -1
- package/plugin/role-agents/video-summarizer/AGENT.md +208 -108
- package/plugin/skills/_shared/proactive-routing.md +2 -1
- package/plugin/skills/research/references/design.md +1 -1
- package/plugin/skills/video-digest/SKILL.md +430 -0
- package/plugin/skills/video-digest/templates/background.md +15 -0
- package/plugin/skills/video-digest/templates/blog.md +16 -0
- package/plugin/skills/video-digest/templates/comments.md +19 -0
- package/plugin/skills/video-digest/templates/counterpoints.md +15 -0
- package/plugin/skills/video-digest/templates/flashcards.md +15 -0
- package/plugin/skills/video-digest/templates/interview.md +15 -0
- package/plugin/skills/video-digest/templates/meeting.md +15 -0
- package/plugin/skills/video-digest/templates/mindmap.md +16 -0
- package/plugin/skills/video-digest/templates/quiz.md +16 -0
- package/plugin/skills/video-digest/templates/quotes.md +16 -0
- package/plugin/skills/video-digest/templates/resources.md +14 -0
- package/plugin/skills/video-digest/templates/timestamps.md +14 -0
- package/plugin/skills/video-digest/templates/visual.md +22 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { extensionKey, extensionSchema } from './blocks.js';
|
|
2
|
+
import { commentsMap } from './comments.js';
|
|
3
|
+
import { sceneSecs } from './scenes.js';
|
|
4
|
+
import { lintDigest, lintErrors } from './lint.js';
|
|
5
|
+
import { reportKey, upsertReport } from './reports.js';
|
|
6
|
+
import { DIGEST_SCHEMA_VERSION, parseDigest, reportSchema } from './schema.js';
|
|
7
|
+
import { DigestStore } from './store.js';
|
|
8
|
+
/**
|
|
9
|
+
* 새로 만든 리포트 한 벌을 digest.json에 합친다. 같은 키가 있으면 바꿔 끼운다.
|
|
10
|
+
* 합친 결과가 lint 오류를 내면 저장하지 않는다. 원래 digest.json을 깨뜨리지 않으려는 것이다
|
|
11
|
+
*/
|
|
12
|
+
export function mergeReport(repoRoot, slug, input) {
|
|
13
|
+
const store = new DigestStore(repoRoot);
|
|
14
|
+
const t = store.readTranscript(slug);
|
|
15
|
+
if (!t.ok)
|
|
16
|
+
throw new Error(t.message);
|
|
17
|
+
const parsedReport = reportSchema.safeParse(input);
|
|
18
|
+
if (!parsedReport.success) {
|
|
19
|
+
const issues = parsedReport.error.issues.map((i) => `${i.path.join('.') || '(root)'}: ${i.message}`);
|
|
20
|
+
throw new Error(`리포트 형식 오류: ${issues.join('; ')}`);
|
|
21
|
+
}
|
|
22
|
+
const report = parsedReport.data;
|
|
23
|
+
const raw = store.readDigestFile(slug);
|
|
24
|
+
let base = null;
|
|
25
|
+
if (raw.kind === 'ok') {
|
|
26
|
+
const parsed = parseDigest(raw.value);
|
|
27
|
+
if (!parsed.ok)
|
|
28
|
+
throw parsed.error;
|
|
29
|
+
base = parsed.value;
|
|
30
|
+
}
|
|
31
|
+
else if (raw.kind !== 'missing') {
|
|
32
|
+
throw new Error(raw.message);
|
|
33
|
+
}
|
|
34
|
+
const { digest, replaced } = base
|
|
35
|
+
? upsertReport(base, report)
|
|
36
|
+
: {
|
|
37
|
+
digest: { schemaVersion: DIGEST_SCHEMA_VERSION, slug, reports: [report], extensions: [] },
|
|
38
|
+
replaced: false,
|
|
39
|
+
};
|
|
40
|
+
const merged = {
|
|
41
|
+
...digest,
|
|
42
|
+
schemaVersion: DIGEST_SCHEMA_VERSION,
|
|
43
|
+
generatedAt: new Date().toISOString(),
|
|
44
|
+
};
|
|
45
|
+
// shots 시각은 후보 장면이어야 합친다. 파일이 없으면 null이라 shots가 있는 리포트는 오류로 걸린다
|
|
46
|
+
const issues = lintDigest(merged, t.transcript, { scenes: sceneSecs(store, slug) });
|
|
47
|
+
const saved = lintErrors(issues).length === 0;
|
|
48
|
+
const digestPath = saved ? store.saveDigest(merged) : store.digestPath(slug);
|
|
49
|
+
const key = reportKey(report.options);
|
|
50
|
+
const index = merged.reports.findIndex((r) => reportKey(r.options) === key);
|
|
51
|
+
return { slug, key, index, replaced, saved, digestPath, issues };
|
|
52
|
+
}
|
|
53
|
+
/** 같은 템플릿과 언어의 확장이 있으면 그 자리에 바꿔 끼우고 없으면 뒤에 붙인다 */
|
|
54
|
+
export function upsertExtension(digest, ext) {
|
|
55
|
+
const key = extensionKey(ext);
|
|
56
|
+
const at = digest.extensions.findIndex((x) => extensionKey(x) === key);
|
|
57
|
+
const extensions = [...digest.extensions];
|
|
58
|
+
if (at >= 0)
|
|
59
|
+
extensions[at] = ext;
|
|
60
|
+
else
|
|
61
|
+
extensions.push(ext);
|
|
62
|
+
return { digest: { ...digest, extensions }, replaced: at >= 0 };
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* 템플릿으로 만든 확장 리포트 한 벌을 digest.json에 합친다.
|
|
66
|
+
* 확장은 요약 리포트에 기대지 않지만 뷰어가 요약 탭을 먼저 열어서 digest.json이 있어야 한다
|
|
67
|
+
*/
|
|
68
|
+
export function mergeExtension(repoRoot, slug, input, opts = {}) {
|
|
69
|
+
const store = new DigestStore(repoRoot);
|
|
70
|
+
const t = store.readTranscript(slug);
|
|
71
|
+
if (!t.ok)
|
|
72
|
+
throw new Error(t.message);
|
|
73
|
+
const parsedExt = extensionSchema.safeParse(input);
|
|
74
|
+
if (!parsedExt.success) {
|
|
75
|
+
const issues = parsedExt.error.issues.map((i) => `${i.path.join('.') || '(root)'}: ${i.message}`);
|
|
76
|
+
throw new Error(`확장 리포트 형식 오류: ${issues.join('; ')}`);
|
|
77
|
+
}
|
|
78
|
+
const ext = parsedExt.data;
|
|
79
|
+
const raw = store.readDigestFile(slug);
|
|
80
|
+
if (raw.kind === 'missing') {
|
|
81
|
+
throw new Error(`digest.json이 없어요. 요약 리포트를 먼저 합쳐 주세요: ${slug}`);
|
|
82
|
+
}
|
|
83
|
+
if (raw.kind !== 'ok')
|
|
84
|
+
throw new Error(raw.message);
|
|
85
|
+
const parsed = parseDigest(raw.value);
|
|
86
|
+
if (!parsed.ok)
|
|
87
|
+
throw parsed.error;
|
|
88
|
+
const { digest, replaced } = upsertExtension(parsed.value, ext);
|
|
89
|
+
const merged = {
|
|
90
|
+
...digest,
|
|
91
|
+
schemaVersion: DIGEST_SCHEMA_VERSION,
|
|
92
|
+
generatedAt: new Date().toISOString(),
|
|
93
|
+
};
|
|
94
|
+
// 댓글 예시는 받아 둔 댓글과 맞아야 합친다. 파일이 없으면 null이라 댓글 블록이 오류로 걸린다
|
|
95
|
+
const issues = lintDigest(merged, t.transcript, {
|
|
96
|
+
...opts,
|
|
97
|
+
comments: opts.comments !== undefined ? opts.comments : commentsMap(store, slug),
|
|
98
|
+
strictVerbatim: true,
|
|
99
|
+
scenes: sceneSecs(store, slug),
|
|
100
|
+
});
|
|
101
|
+
const saved = lintErrors(issues).length === 0;
|
|
102
|
+
const digestPath = saved ? store.saveDigest(merged) : store.digestPath(slug);
|
|
103
|
+
const key = extensionKey(ext);
|
|
104
|
+
const index = merged.extensions.findIndex((x) => extensionKey(x) === key);
|
|
105
|
+
return { slug, key, index, replaced, saved, digestPath, issues };
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=merge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"merge.js","sourceRoot":"","sources":["../../../src/digest/merge.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { type RunCommand } from './ytdlp.js';
|
|
3
|
+
export declare const OCR_FILE = "ocr.json";
|
|
4
|
+
export declare const DEFAULT_OCR_INTERVAL_SEC = 10;
|
|
5
|
+
/** 긴 영상에 짧은 간격을 주면 PNG가 수만 장 쌓여서 이보다 많으면 간격을 넓힌다 */
|
|
6
|
+
export declare const MAX_OCR_FRAMES = 600;
|
|
7
|
+
/** tesseract 언어 이름. kor+eng처럼 +로 잇는다 */
|
|
8
|
+
export declare const TESSERACT_LANG_PATTERN: RegExp;
|
|
9
|
+
/**
|
|
10
|
+
* 화면 글자 한 벌. 영상 화면에서 읽은 글자라 자막만큼 믿지 않는다.
|
|
11
|
+
* 요약 에이전트가 보조 자료로만 읽고 렌더러는 이 파일을 쓰지 않는다
|
|
12
|
+
*/
|
|
13
|
+
export declare const ocrFileSchema: z.ZodObject<{
|
|
14
|
+
engine: z.ZodLiteral<"tesseract">;
|
|
15
|
+
lang: z.ZodString;
|
|
16
|
+
intervalSec: z.ZodNumber;
|
|
17
|
+
/** 프레임 상한 때문에 간격을 넓혔으면 처음 요청한 간격 */
|
|
18
|
+
requestedIntervalSec: z.ZodOptional<z.ZodNumber>;
|
|
19
|
+
items: z.ZodArray<z.ZodObject<{
|
|
20
|
+
sec: z.ZodNumber;
|
|
21
|
+
i: z.ZodNumber;
|
|
22
|
+
text: z.ZodString;
|
|
23
|
+
truncated: z.ZodOptional<z.ZodBoolean>;
|
|
24
|
+
}, "strip", z.ZodTypeAny, {
|
|
25
|
+
text: string;
|
|
26
|
+
i: number;
|
|
27
|
+
sec: number;
|
|
28
|
+
truncated?: boolean | undefined;
|
|
29
|
+
}, {
|
|
30
|
+
text: string;
|
|
31
|
+
i: number;
|
|
32
|
+
sec: number;
|
|
33
|
+
truncated?: boolean | undefined;
|
|
34
|
+
}>, "many">;
|
|
35
|
+
}, "strip", z.ZodTypeAny, {
|
|
36
|
+
items: {
|
|
37
|
+
text: string;
|
|
38
|
+
i: number;
|
|
39
|
+
sec: number;
|
|
40
|
+
truncated?: boolean | undefined;
|
|
41
|
+
}[];
|
|
42
|
+
engine: "tesseract";
|
|
43
|
+
lang: string;
|
|
44
|
+
intervalSec: number;
|
|
45
|
+
requestedIntervalSec?: number | undefined;
|
|
46
|
+
}, {
|
|
47
|
+
items: {
|
|
48
|
+
text: string;
|
|
49
|
+
i: number;
|
|
50
|
+
sec: number;
|
|
51
|
+
truncated?: boolean | undefined;
|
|
52
|
+
}[];
|
|
53
|
+
engine: "tesseract";
|
|
54
|
+
lang: string;
|
|
55
|
+
intervalSec: number;
|
|
56
|
+
requestedIntervalSec?: number | undefined;
|
|
57
|
+
}>;
|
|
58
|
+
export type OcrFile = z.infer<typeof ocrFileSchema>;
|
|
59
|
+
export interface OcrOptions {
|
|
60
|
+
repoRoot: string;
|
|
61
|
+
intervalSec?: number;
|
|
62
|
+
/** tesseract 언어. 비우면 스크립트 언어에서 고른다 */
|
|
63
|
+
lang?: string;
|
|
64
|
+
run?: RunCommand;
|
|
65
|
+
}
|
|
66
|
+
export interface OcrResult {
|
|
67
|
+
path: string;
|
|
68
|
+
lang: string;
|
|
69
|
+
intervalSec: number;
|
|
70
|
+
/** 프레임 상한 때문에 간격을 넓혔으면 처음 요청한 간격 */
|
|
71
|
+
requestedIntervalSec?: number;
|
|
72
|
+
frames: number;
|
|
73
|
+
items: number;
|
|
74
|
+
/** 글자 상한에서 잘린 항목 수 */
|
|
75
|
+
truncated: number;
|
|
76
|
+
/** showinfo 로그에서 받은 시각 수가 프레임 수보다 모자라면 그 시각 수. 모자란 프레임은 간격으로 시각을 어림했다 */
|
|
77
|
+
loggedTimes?: number;
|
|
78
|
+
}
|
|
79
|
+
/** 화면 글자에는 코드와 제품 이름 같은 영문이 섞여서 영어가 아닌 언어에도 eng를 붙인다 */
|
|
80
|
+
export declare function tesseractLang(lang: string): string;
|
|
81
|
+
/** 이보다 낮은 신뢰도의 단어는 아이콘이나 지도 무늬를 글자로 읽은 경우가 대부분이다 */
|
|
82
|
+
export declare const MIN_WORD_CONFIDENCE = 70;
|
|
83
|
+
/**
|
|
84
|
+
* tesseract tsv 출력을 줄 단위 글자로 바꾼다. 신뢰도가 낮은 단어와 글자 하나짜리 단어는 버리고
|
|
85
|
+
* 글자나 숫자가 두 개 안 남은 줄도 버린다
|
|
86
|
+
*/
|
|
87
|
+
export declare function textFromTsv(tsv: string, minConfidence?: number): string;
|
|
88
|
+
/** 상한을 넘는 글자는 줄 단위로 자르고 끝에 …를 붙인다. 줄 하나가 상한보다 길면 그 줄을 자른다 */
|
|
89
|
+
export declare function clipOcrText(text: string, max?: number): {
|
|
90
|
+
text: string;
|
|
91
|
+
truncated: boolean;
|
|
92
|
+
};
|
|
93
|
+
/** 간격마다 첫 프레임만 고르는 select 식. -vsync vfr과 함께 써야 빈 자리를 복제하지 않는다 */
|
|
94
|
+
export declare function everyInterval(intervalSec: number): string;
|
|
95
|
+
/**
|
|
96
|
+
* intervalSec마다 프레임을 뽑아 화면 글자를 읽고 ocr.json에 둔다.
|
|
97
|
+
* 슬라이드처럼 한동안 그대로인 화면은 앞 항목과 글자가 같아서 한 번만 남긴다
|
|
98
|
+
*/
|
|
99
|
+
export declare function runOcr(slug: string, opts: OcrOptions): Promise<OcrResult>;
|
|
100
|
+
//# sourceMappingURL=ocr.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ocr.d.ts","sourceRoot":"","sources":["../../../src/digest/ocr.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,OAAO,EAAwC,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAEnF,eAAO,MAAM,QAAQ,aAAa,CAAC;AACnC,eAAO,MAAM,wBAAwB,KAAK,CAAC;AAE3C,oDAAoD;AACpD,eAAO,MAAM,cAAc,MAAM,CAAC;AAElC,wCAAwC;AACxC,eAAO,MAAM,sBAAsB,QAA8C,CAAC;AAElF;;;GAGG;AACH,eAAO,MAAM,aAAa;;;;IAIxB,oCAAoC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAWpC,CAAC;AAEH,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAEpD,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sCAAsC;IACtC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,UAAU,CAAC;CAClB;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,oCAAoC;IACpC,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,sBAAsB;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,yEAAyE;IACzE,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AASD,wDAAwD;AACxD,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAElD;AAED,oDAAoD;AACpD,eAAO,MAAM,mBAAmB,KAAK,CAAC;AAEtC;;;GAGG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,aAAa,SAAsB,GAAG,MAAM,CAcpF;AAED,6DAA6D;AAC7D,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,SAAW,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAU9F;AAID,iEAAiE;AACjE,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED;;;GAGG;AACH,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CA+F/E"}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { mkdtempSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { ensureVideo, parseShowinfo } from './frames.js';
|
|
6
|
+
import { DigestStore } from './store.js';
|
|
7
|
+
import { findSegmentIndex } from './viewer-logic.js';
|
|
8
|
+
import { CommandError, runCommand, timeoutFor } from './ytdlp.js';
|
|
9
|
+
export const OCR_FILE = 'ocr.json';
|
|
10
|
+
export const DEFAULT_OCR_INTERVAL_SEC = 10;
|
|
11
|
+
const MAX_TEXT = 2000;
|
|
12
|
+
/** 긴 영상에 짧은 간격을 주면 PNG가 수만 장 쌓여서 이보다 많으면 간격을 넓힌다 */
|
|
13
|
+
export const MAX_OCR_FRAMES = 600;
|
|
14
|
+
/** tesseract 언어 이름. kor+eng처럼 +로 잇는다 */
|
|
15
|
+
export const TESSERACT_LANG_PATTERN = /^[a-z][a-z_]{1,15}(\+[a-z][a-z_]{1,15})*$/;
|
|
16
|
+
/**
|
|
17
|
+
* 화면 글자 한 벌. 영상 화면에서 읽은 글자라 자막만큼 믿지 않는다.
|
|
18
|
+
* 요약 에이전트가 보조 자료로만 읽고 렌더러는 이 파일을 쓰지 않는다
|
|
19
|
+
*/
|
|
20
|
+
export const ocrFileSchema = z.object({
|
|
21
|
+
engine: z.literal('tesseract'),
|
|
22
|
+
lang: z.string().regex(TESSERACT_LANG_PATTERN),
|
|
23
|
+
intervalSec: z.number().positive(),
|
|
24
|
+
/** 프레임 상한 때문에 간격을 넓혔으면 처음 요청한 간격 */
|
|
25
|
+
requestedIntervalSec: z.number().positive().optional(),
|
|
26
|
+
items: z.array(z.object({
|
|
27
|
+
sec: z.number().nonnegative(),
|
|
28
|
+
i: z.number().int().nonnegative(),
|
|
29
|
+
// 글자 상한에서 잘렸으면 마지막 줄 끝에 …가 붙고 truncated가 true다
|
|
30
|
+
text: z.string().min(1).max(MAX_TEXT),
|
|
31
|
+
truncated: z.boolean().optional(),
|
|
32
|
+
})),
|
|
33
|
+
});
|
|
34
|
+
const TESSERACT_BY_LANG = {
|
|
35
|
+
ko: 'kor+eng',
|
|
36
|
+
ja: 'jpn+eng',
|
|
37
|
+
zh: 'chi_sim+eng',
|
|
38
|
+
en: 'eng',
|
|
39
|
+
};
|
|
40
|
+
/** 화면 글자에는 코드와 제품 이름 같은 영문이 섞여서 영어가 아닌 언어에도 eng를 붙인다 */
|
|
41
|
+
export function tesseractLang(lang) {
|
|
42
|
+
return TESSERACT_BY_LANG[lang.toLowerCase().split(/[-_]/)[0]] ?? 'eng';
|
|
43
|
+
}
|
|
44
|
+
/** 이보다 낮은 신뢰도의 단어는 아이콘이나 지도 무늬를 글자로 읽은 경우가 대부분이다 */
|
|
45
|
+
export const MIN_WORD_CONFIDENCE = 70;
|
|
46
|
+
/**
|
|
47
|
+
* tesseract tsv 출력을 줄 단위 글자로 바꾼다. 신뢰도가 낮은 단어와 글자 하나짜리 단어는 버리고
|
|
48
|
+
* 글자나 숫자가 두 개 안 남은 줄도 버린다
|
|
49
|
+
*/
|
|
50
|
+
export function textFromTsv(tsv, minConfidence = MIN_WORD_CONFIDENCE) {
|
|
51
|
+
const lines = new Map();
|
|
52
|
+
for (const row of tsv.split(/\r?\n/).slice(1)) {
|
|
53
|
+
const cols = row.split('\t');
|
|
54
|
+
if (cols.length < 12)
|
|
55
|
+
continue;
|
|
56
|
+
const word = cols.slice(11).join(' ').trim();
|
|
57
|
+
if (Number(cols[10]) < minConfidence)
|
|
58
|
+
continue;
|
|
59
|
+
if ((word.match(/[\p{L}\p{N}]/gu) ?? []).length < 2)
|
|
60
|
+
continue;
|
|
61
|
+
const key = `${cols[2]}-${cols[3]}-${cols[4]}`;
|
|
62
|
+
const words = lines.get(key) ?? [];
|
|
63
|
+
words.push(word);
|
|
64
|
+
lines.set(key, words);
|
|
65
|
+
}
|
|
66
|
+
return [...lines.values()].map((w) => w.join(' ')).join('\n');
|
|
67
|
+
}
|
|
68
|
+
/** 상한을 넘는 글자는 줄 단위로 자르고 끝에 …를 붙인다. 줄 하나가 상한보다 길면 그 줄을 자른다 */
|
|
69
|
+
export function clipOcrText(text, max = MAX_TEXT) {
|
|
70
|
+
if (text.length <= max)
|
|
71
|
+
return { text, truncated: false };
|
|
72
|
+
let out = '';
|
|
73
|
+
for (const line of text.split('\n')) {
|
|
74
|
+
const next = out ? `${out}\n${line}` : line;
|
|
75
|
+
if (next.length > max - 1)
|
|
76
|
+
break;
|
|
77
|
+
out = next;
|
|
78
|
+
}
|
|
79
|
+
if (!out)
|
|
80
|
+
out = text.slice(0, max - 1);
|
|
81
|
+
return { text: `${out}…`, truncated: true };
|
|
82
|
+
}
|
|
83
|
+
const FRAME_FILE = /^f(\d{5})\.png$/;
|
|
84
|
+
/** 간격마다 첫 프레임만 고르는 select 식. -vsync vfr과 함께 써야 빈 자리를 복제하지 않는다 */
|
|
85
|
+
export function everyInterval(intervalSec) {
|
|
86
|
+
return `select='isnan(prev_selected_t)+gte(floor(t/${intervalSec})-floor(prev_selected_t/${intervalSec}),1)'`;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* intervalSec마다 프레임을 뽑아 화면 글자를 읽고 ocr.json에 둔다.
|
|
90
|
+
* 슬라이드처럼 한동안 그대로인 화면은 앞 항목과 글자가 같아서 한 번만 남긴다
|
|
91
|
+
*/
|
|
92
|
+
export async function runOcr(slug, opts) {
|
|
93
|
+
const run = opts.run ?? runCommand;
|
|
94
|
+
let intervalSec = opts.intervalSec ?? DEFAULT_OCR_INTERVAL_SEC;
|
|
95
|
+
if (!Number.isFinite(intervalSec) || intervalSec < 1 || intervalSec > 600) {
|
|
96
|
+
throw new Error(`OCR 간격은 1~600초여야 해요: ${intervalSec}`);
|
|
97
|
+
}
|
|
98
|
+
const requestedIntervalSec = intervalSec;
|
|
99
|
+
const store = new DigestStore(opts.repoRoot);
|
|
100
|
+
const t = store.readTranscript(slug);
|
|
101
|
+
if (!t.ok)
|
|
102
|
+
throw new Error(t.message);
|
|
103
|
+
const transcript = t.transcript;
|
|
104
|
+
intervalSec = Math.max(intervalSec, Math.ceil(transcript.meta.durationSec / MAX_OCR_FRAMES));
|
|
105
|
+
const widened = intervalSec !== requestedIntervalSec;
|
|
106
|
+
const lang = opts.lang ?? tesseractLang(transcript.meta.lang);
|
|
107
|
+
if (!TESSERACT_LANG_PATTERN.test(lang)) {
|
|
108
|
+
throw new Error(`tesseract 언어는 kor+eng 꼴이어야 해요: ${lang}`);
|
|
109
|
+
}
|
|
110
|
+
const cwd = mkdtempSync(join(tmpdir(), 'gestalt-digest-ocr-'));
|
|
111
|
+
try {
|
|
112
|
+
const video = await ensureVideo(run, transcript, store.videoCacheDir(slug), cwd);
|
|
113
|
+
const { stderr } = await run('ffmpeg', [
|
|
114
|
+
...['-hide_banner', '-nostats', '-y', '-i', video],
|
|
115
|
+
// fps 필터는 두 번째 장부터 간격 절반만큼 늦은 프레임을 내서 sec과 화면이 어긋난다.
|
|
116
|
+
// 간격마다 첫 프레임을 고르고 그 프레임의 실제 시각은 showinfo 로그에서 받는다.
|
|
117
|
+
// 작은 글자는 tesseract가 못 읽어서 1280보다 좁은 화면은 두 배로 키운다
|
|
118
|
+
...['-vf', `${everyInterval(intervalSec)},showinfo,scale='if(lt(iw,1280),iw*2,iw)':-2`],
|
|
119
|
+
// -fps_mode는 ffmpeg 5.1부터라 그 전 버전에서도 도는 -vsync를 쓴다
|
|
120
|
+
...['-vsync', 'vfr', join(cwd, 'f%05d.png')],
|
|
121
|
+
], { cwd, timeoutMs: timeoutFor(transcript.meta.durationSec, 0.2) });
|
|
122
|
+
const times = parseShowinfo(stderr);
|
|
123
|
+
const frames = readdirSync(cwd)
|
|
124
|
+
.filter((f) => FRAME_FILE.test(f))
|
|
125
|
+
.sort();
|
|
126
|
+
const starts = transcript.segments.map((s) => s.startSec);
|
|
127
|
+
const items = [];
|
|
128
|
+
let prev = '';
|
|
129
|
+
let truncated = 0;
|
|
130
|
+
for (const f of frames) {
|
|
131
|
+
const n = Number(FRAME_FILE.exec(f)[1]);
|
|
132
|
+
// 로그를 못 읽었으면 간격마다 첫 프레임이라는 가정으로 시각을 잡는다
|
|
133
|
+
const sec = Math.round((times[n - 1] ?? (n - 1) * intervalSec) * 100) / 100;
|
|
134
|
+
let raw;
|
|
135
|
+
try {
|
|
136
|
+
// 화면 녹화는 글자가 흩어져 있어서 문단 분할(기본 psm 3)로는 거의 못 읽는다
|
|
137
|
+
({ stdout: raw } = await run('tesseract', [join(cwd, f), 'stdout', '-l', lang, '--psm', '11', 'tsv'], { cwd, timeoutMs: 60_000 }));
|
|
138
|
+
}
|
|
139
|
+
catch (e) {
|
|
140
|
+
if (e instanceof CommandError && e.message.includes('찾지 못했어요')) {
|
|
141
|
+
throw new Error('tesseract를 찾지 못했어요. brew install tesseract tesseract-lang으로 설치해 주세요.', { cause: e });
|
|
142
|
+
}
|
|
143
|
+
throw e;
|
|
144
|
+
}
|
|
145
|
+
const full = textFromTsv(raw);
|
|
146
|
+
if (!full || full === prev)
|
|
147
|
+
continue;
|
|
148
|
+
prev = full;
|
|
149
|
+
const clip = clipOcrText(full);
|
|
150
|
+
if (clip.truncated)
|
|
151
|
+
truncated += 1;
|
|
152
|
+
items.push({
|
|
153
|
+
sec,
|
|
154
|
+
i: Math.max(0, findSegmentIndex(starts, sec)),
|
|
155
|
+
text: clip.text,
|
|
156
|
+
...(clip.truncated ? { truncated: true } : {}),
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
const widenedFrom = widened ? { requestedIntervalSec } : {};
|
|
160
|
+
const mismatch = times.length < frames.length ? { loggedTimes: times.length } : {};
|
|
161
|
+
const out = { engine: 'tesseract', lang, intervalSec, ...widenedFrom, items };
|
|
162
|
+
const path = join(store.dir(slug), OCR_FILE);
|
|
163
|
+
writeFileSync(path, JSON.stringify(out, null, 2) + '\n');
|
|
164
|
+
return {
|
|
165
|
+
path,
|
|
166
|
+
lang,
|
|
167
|
+
intervalSec,
|
|
168
|
+
...widenedFrom,
|
|
169
|
+
frames: frames.length,
|
|
170
|
+
items: items.length,
|
|
171
|
+
truncated,
|
|
172
|
+
...mismatch,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
finally {
|
|
176
|
+
rmSync(cwd, { recursive: true, force: true });
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
//# sourceMappingURL=ocr.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ocr.js","sourceRoot":"","sources":["../../../src/digest/ocr.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { type Digest } from './schema.js';
|
|
2
|
+
export interface ProseItem {
|
|
3
|
+
path: string;
|
|
4
|
+
text: string;
|
|
5
|
+
/** 원문 sha256 앞 12자. apply가 extract 이후 원문이 바뀌었는지 이걸로 본다 */
|
|
6
|
+
hash: string;
|
|
7
|
+
/** 그림 안 글자. 명사구로 두고 원문보다 길게 쓰지 않는다 */
|
|
8
|
+
kind?: 'label';
|
|
9
|
+
/** 같은 그림에 함께 놓이는 라벨은 같은 값이다. 짝을 맞춰 다듬으라고 준다 */
|
|
10
|
+
group?: string;
|
|
11
|
+
}
|
|
12
|
+
export type ProseWarningCode = 'UNKNOWN_PATH' | 'STALE' | 'MARKER_MISMATCH' | 'EMPTY' | 'DUPLICATE_PATH' | 'LABEL_TOO_LONG';
|
|
13
|
+
export interface ProseWarning {
|
|
14
|
+
path: string;
|
|
15
|
+
code: ProseWarningCode;
|
|
16
|
+
missing?: string[];
|
|
17
|
+
added?: string[];
|
|
18
|
+
}
|
|
19
|
+
export interface ProseEdit {
|
|
20
|
+
path: string;
|
|
21
|
+
text: string;
|
|
22
|
+
hash: string;
|
|
23
|
+
}
|
|
24
|
+
export interface ProseApplyResult {
|
|
25
|
+
digest: Digest;
|
|
26
|
+
applied: number;
|
|
27
|
+
unchanged: number;
|
|
28
|
+
warnings: ProseWarning[];
|
|
29
|
+
}
|
|
30
|
+
export declare function proseHash(text: string): string;
|
|
31
|
+
/** 윤문할 문장과 그림 글자. path는 lint가 쓰는 것과 같아서 경고와 짝을 맞춰 볼 수 있다 */
|
|
32
|
+
export declare function extractProse(digest: Digest): ProseItem[];
|
|
33
|
+
/**
|
|
34
|
+
* 윤문 전후로 그대로 남아야 하는 표식. 키워드는 id만 본다. 표시 글자는 문장에 맞게 바뀌어도 된다.
|
|
35
|
+
* 키워드 마커를 먼저 지워야 id 속 숫자가 수치로 한 번 더 세어지지 않는다.
|
|
36
|
+
*/
|
|
37
|
+
export declare function extractMarkers(text: string): string[];
|
|
38
|
+
export declare function diffMarkers(before: string, after: string): {
|
|
39
|
+
missing: string[];
|
|
40
|
+
added: string[];
|
|
41
|
+
} | null;
|
|
42
|
+
/**
|
|
43
|
+
* 윤문된 문장을 path로 되돌려 넣는다. 검사에 걸린 항목은 원문을 두고 경고만 남긴다.
|
|
44
|
+
* 넘겨받은 digest는 건드리지 않고 복제본을 고쳐 돌려준다.
|
|
45
|
+
*/
|
|
46
|
+
export declare function applyProse(input: Digest, edits: ProseEdit[]): ProseApplyResult;
|
|
47
|
+
//# sourceMappingURL=prose.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prose.d.ts","sourceRoot":"","sources":["../../../src/digest/prose.ts"],"names":[],"mappings":"AAIA,OAAO,EAA+B,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AAEvE,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,0DAA0D;IAC1D,IAAI,EAAE,MAAM,CAAC;IACb,sCAAsC;IACtC,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,+CAA+C;IAC/C,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,MAAM,gBAAgB,GACxB,cAAc,GACd,OAAO,GACP,iBAAiB,GACjB,OAAO,GACP,gBAAgB,GAChB,gBAAgB,CAAC;AAErB,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,gBAAgB,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,YAAY,EAAE,CAAC;CAC1B;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE9C;AAoDD,4DAA4D;AAC5D,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,CAgBxD;AAKD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAgBrD;AAQD,wBAAgB,WAAW,CACzB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,GACZ;IAAE,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,GAAG,IAAI,CAQ/C;AAUD;;;GAGG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,gBAAgB,CAgD9E"}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { collectTextFields } from './lint.js';
|
|
3
|
+
import { walkDiagram, walkMindmap } from './lint-extension.js';
|
|
4
|
+
import { boldPattern, keywordPattern } from './schema.js';
|
|
5
|
+
export function proseHash(text) {
|
|
6
|
+
return createHash('sha256').update(text).digest('hex').slice(0, 12);
|
|
7
|
+
}
|
|
8
|
+
/** 제목은 목차와 미니맵에 그대로 쓰이는 이름이라 윤문 대상에서 뺀다 */
|
|
9
|
+
const isTitle = (path) => path.endsWith('.title');
|
|
10
|
+
function diagramLabels(d, dp) {
|
|
11
|
+
const out = [];
|
|
12
|
+
if (d.caption)
|
|
13
|
+
out.push({ path: `${dp}.caption`, text: d.caption, group: dp });
|
|
14
|
+
for (const node of walkDiagram(d, dp)) {
|
|
15
|
+
out.push({ path: node.labelPath, text: node.label, group: dp });
|
|
16
|
+
}
|
|
17
|
+
return out;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* 도식 라벨과 설명, 장면 설명, 마인드맵 라벨. 칸이 좁아 본문과 다른 기준으로 다듬어서 따로 모은다.
|
|
21
|
+
* 열 제목과 전후 비교 제목은 path가 `.title`로 끝나도 그림 안 글자라 여기 넣는다
|
|
22
|
+
*/
|
|
23
|
+
function collectLabelFields(digest) {
|
|
24
|
+
const out = [];
|
|
25
|
+
digest.reports.forEach((report, ri) => {
|
|
26
|
+
report.sections.forEach((section, si) => {
|
|
27
|
+
const sp = `reports.${ri}.sections.${si}`;
|
|
28
|
+
if (section.diagram)
|
|
29
|
+
out.push(...diagramLabels(section.diagram, `${sp}.diagram`));
|
|
30
|
+
section.subsections.forEach((sub, bi) => {
|
|
31
|
+
const group = `${sp}.subsections.${bi}.shots`;
|
|
32
|
+
sub.shots?.forEach((shot, k) => {
|
|
33
|
+
out.push({ path: `${group}.${k}.caption`, text: shot.caption, group });
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
digest.extensions.forEach((ext, xi) => {
|
|
39
|
+
ext.blocks.forEach((block, bi) => {
|
|
40
|
+
const bp = `extensions.${xi}.blocks.${bi}`;
|
|
41
|
+
if (block.type === 'figure')
|
|
42
|
+
out.push(...diagramLabels(block.diagram, `${bp}.diagram`));
|
|
43
|
+
if (block.type === 'mindmap') {
|
|
44
|
+
for (const { node, path } of walkMindmap(block.root, `${bp}.root`)) {
|
|
45
|
+
out.push({ path: `${path}.label`, text: node.label, group: bp });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
52
|
+
/** 윤문할 문장과 그림 글자. path는 lint가 쓰는 것과 같아서 경고와 짝을 맞춰 볼 수 있다 */
|
|
53
|
+
export function extractProse(digest) {
|
|
54
|
+
const prose = collectTextFields(digest)
|
|
55
|
+
.filter((f) => !isTitle(f.path) && f.text.trim())
|
|
56
|
+
.map((f) => ({ path: f.path, text: f.text, hash: proseHash(f.text) }));
|
|
57
|
+
const labels = collectLabelFields(digest)
|
|
58
|
+
.filter((f) => f.text.trim())
|
|
59
|
+
.map((f) => ({
|
|
60
|
+
path: f.path,
|
|
61
|
+
text: f.text,
|
|
62
|
+
hash: proseHash(f.text),
|
|
63
|
+
kind: 'label',
|
|
64
|
+
group: f.group,
|
|
65
|
+
}));
|
|
66
|
+
return [...prose, ...labels];
|
|
67
|
+
}
|
|
68
|
+
/** 그림 칸은 원문 길이에 맞춰 잡혀 있어서 라벨이 길어지면 말줄임이나 줄넘김이 생긴다 */
|
|
69
|
+
const charCount = (text) => [...text].length;
|
|
70
|
+
/**
|
|
71
|
+
* 윤문 전후로 그대로 남아야 하는 표식. 키워드는 id만 본다. 표시 글자는 문장에 맞게 바뀌어도 된다.
|
|
72
|
+
* 키워드 마커를 먼저 지워야 id 속 숫자가 수치로 한 번 더 세어지지 않는다.
|
|
73
|
+
*/
|
|
74
|
+
export function extractMarkers(text) {
|
|
75
|
+
const markers = [];
|
|
76
|
+
let rest = text.replace(keywordPattern(), (_m, id, label) => {
|
|
77
|
+
markers.push(`키워드:${id}`);
|
|
78
|
+
return ` ${label} `;
|
|
79
|
+
});
|
|
80
|
+
rest = rest.replace(boldPattern(), (_m, inner) => {
|
|
81
|
+
markers.push('굵게');
|
|
82
|
+
return ` ${inner} `;
|
|
83
|
+
});
|
|
84
|
+
rest = rest.replace(/https?:\/\/[^\s)]+/g, (m) => {
|
|
85
|
+
markers.push(`링크:${m}`);
|
|
86
|
+
return ' ';
|
|
87
|
+
});
|
|
88
|
+
for (const m of rest.match(/\d(?:[\d,]*\d)?(?:\.\d+)?%?/g) ?? [])
|
|
89
|
+
markers.push(`수치:${m}`);
|
|
90
|
+
return markers;
|
|
91
|
+
}
|
|
92
|
+
function countOf(list) {
|
|
93
|
+
const counts = new Map();
|
|
94
|
+
for (const m of list)
|
|
95
|
+
counts.set(m, (counts.get(m) ?? 0) + 1);
|
|
96
|
+
return counts;
|
|
97
|
+
}
|
|
98
|
+
export function diffMarkers(before, after) {
|
|
99
|
+
const a = countOf(extractMarkers(before));
|
|
100
|
+
const b = countOf(extractMarkers(after));
|
|
101
|
+
const missing = [];
|
|
102
|
+
const added = [];
|
|
103
|
+
for (const [m, n] of a)
|
|
104
|
+
for (let k = b.get(m) ?? 0; k < n; k++)
|
|
105
|
+
missing.push(m);
|
|
106
|
+
for (const [m, n] of b)
|
|
107
|
+
for (let k = a.get(m) ?? 0; k < n; k++)
|
|
108
|
+
added.push(m);
|
|
109
|
+
return missing.length || added.length ? { missing, added } : null;
|
|
110
|
+
}
|
|
111
|
+
/** `reports.0.sections.0.conclusion` 같은 path의 마지막 칸에 값을 넣는다 */
|
|
112
|
+
function setAt(root, path, value) {
|
|
113
|
+
const keys = path.split('.');
|
|
114
|
+
let node = root;
|
|
115
|
+
for (const k of keys.slice(0, -1))
|
|
116
|
+
node = node[k];
|
|
117
|
+
node[keys[keys.length - 1]] = value;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* 윤문된 문장을 path로 되돌려 넣는다. 검사에 걸린 항목은 원문을 두고 경고만 남긴다.
|
|
121
|
+
* 넘겨받은 digest는 건드리지 않고 복제본을 고쳐 돌려준다.
|
|
122
|
+
*/
|
|
123
|
+
export function applyProse(input, edits) {
|
|
124
|
+
const digest = structuredClone(input);
|
|
125
|
+
const current = new Map(extractProse(digest).map((i) => [i.path, i]));
|
|
126
|
+
const pathCounts = countOf(edits.map((e) => e.path));
|
|
127
|
+
const warnings = [];
|
|
128
|
+
const reported = new Set();
|
|
129
|
+
let applied = 0;
|
|
130
|
+
let unchanged = 0;
|
|
131
|
+
for (const edit of edits) {
|
|
132
|
+
const { path } = edit;
|
|
133
|
+
// 같은 path가 둘 이상이면 어느 쪽이 맞는지 모르니 전부 원문을 둔다
|
|
134
|
+
if ((pathCounts.get(path) ?? 0) > 1) {
|
|
135
|
+
if (!reported.has(path))
|
|
136
|
+
warnings.push({ path, code: 'DUPLICATE_PATH' });
|
|
137
|
+
reported.add(path);
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
const item = current.get(path);
|
|
141
|
+
if (!item) {
|
|
142
|
+
warnings.push({ path, code: 'UNKNOWN_PATH' });
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
if (item.hash !== edit.hash) {
|
|
146
|
+
warnings.push({ path, code: 'STALE' });
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (!edit.text.trim()) {
|
|
150
|
+
warnings.push({ path, code: 'EMPTY' });
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
const diff = diffMarkers(item.text, edit.text);
|
|
154
|
+
if (diff) {
|
|
155
|
+
warnings.push({ path, code: 'MARKER_MISMATCH', ...diff });
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (item.kind === 'label' && charCount(edit.text) > charCount(item.text)) {
|
|
159
|
+
warnings.push({ path, code: 'LABEL_TOO_LONG' });
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
if (edit.text === item.text) {
|
|
163
|
+
unchanged++;
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
setAt(digest, path, edit.text);
|
|
167
|
+
applied++;
|
|
168
|
+
}
|
|
169
|
+
return { digest, applied, unchanged, warnings };
|
|
170
|
+
}
|
|
171
|
+
//# sourceMappingURL=prose.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prose.js","sourceRoot":"","sources":["../../../src/digest/prose.ts"],"names":[],"mappings":"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"}
|