@tienne/gestalt 0.103.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/dist/package.json +1 -1
- package/dist/plugin/role-agents/video-summarizer/AGENT.md +47 -11
- package/dist/plugin/skills/video-digest/SKILL.md +53 -31
- package/dist/plugin/skills/video-digest/templates/visual.md +22 -0
- package/dist/src/cli/commands/digest.d.ts +3 -0
- package/dist/src/cli/commands/digest.d.ts.map +1 -1
- package/dist/src/cli/commands/digest.js +34 -1
- package/dist/src/cli/commands/digest.js.map +1 -1
- package/dist/src/cli/index.d.ts.map +1 -1
- package/dist/src/cli/index.js +6 -2
- package/dist/src/cli/index.js.map +1 -1
- package/dist/src/digest/blocks.d.ts +3875 -504
- package/dist/src/digest/blocks.d.ts.map +1 -1
- package/dist/src/digest/blocks.js +14 -6
- package/dist/src/digest/blocks.js.map +1 -1
- 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/frames.d.ts +21 -1
- package/dist/src/digest/frames.d.ts.map +1 -1
- package/dist/src/digest/frames.js +124 -27
- package/dist/src/digest/frames.js.map +1 -1
- package/dist/src/digest/index.d.ts +1 -0
- package/dist/src/digest/index.d.ts.map +1 -1
- package/dist/src/digest/index.js +1 -0
- package/dist/src/digest/index.js.map +1 -1
- package/dist/src/digest/lint-extension.d.ts +16 -2
- package/dist/src/digest/lint-extension.d.ts.map +1 -1
- package/dist/src/digest/lint-extension.js +122 -5
- package/dist/src/digest/lint-extension.js.map +1 -1
- package/dist/src/digest/lint.d.ts +15 -5
- package/dist/src/digest/lint.d.ts.map +1 -1
- package/dist/src/digest/lint.js +93 -50
- package/dist/src/digest/lint.js.map +1 -1
- package/dist/src/digest/merge.d.ts.map +1 -1
- package/dist/src/digest/merge.js +4 -1
- package/dist/src/digest/merge.js.map +1 -1
- package/dist/src/digest/ocr.d.ts +4 -0
- package/dist/src/digest/ocr.d.ts.map +1 -1
- package/dist/src/digest/ocr.js +17 -6
- package/dist/src/digest/ocr.js.map +1 -1
- package/dist/src/digest/prose.d.ts +6 -2
- package/dist/src/digest/prose.d.ts.map +1 -1
- package/dist/src/digest/prose.js +61 -2
- package/dist/src/digest/prose.js.map +1 -1
- package/dist/src/digest/render-blocks.d.ts +7 -1
- package/dist/src/digest/render-blocks.d.ts.map +1 -1
- package/dist/src/digest/render-blocks.js +107 -21
- package/dist/src/digest/render-blocks.js.map +1 -1
- package/dist/src/digest/render-html.d.ts.map +1 -1
- package/dist/src/digest/render-html.js +47 -8
- package/dist/src/digest/render-html.js.map +1 -1
- package/dist/src/digest/render-md.d.ts.map +1 -1
- package/dist/src/digest/render-md.js +15 -2
- package/dist/src/digest/render-md.js.map +1 -1
- package/dist/src/digest/render.d.ts.map +1 -1
- package/dist/src/digest/render.js +4 -1
- package/dist/src/digest/render.js.map +1 -1
- 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.d.ts +10458 -3285
- package/dist/src/digest/schema.d.ts.map +1 -1
- package/dist/src/digest/schema.js +16 -30
- package/dist/src/digest/schema.js.map +1 -1
- package/dist/src/digest/svg.d.ts +30 -0
- package/dist/src/digest/svg.d.ts.map +1 -1
- package/dist/src/digest/svg.js +151 -0
- package/dist/src/digest/svg.js.map +1 -1
- package/dist/src/digest/viewer-js.d.ts.map +1 -1
- package/dist/src/digest/viewer-js.js +29 -4
- package/dist/src/digest/viewer-js.js.map +1 -1
- package/dist/src/mcp/schemas.js +1 -1
- package/dist/src/mcp/schemas.js.map +1 -1
- package/dist/src/mcp/tools/digest-passthrough.d.ts.map +1 -1
- package/dist/src/mcp/tools/digest-passthrough.js +3 -0
- package/dist/src/mcp/tools/digest-passthrough.js.map +1 -1
- 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 +47 -11
- package/plugin/skills/video-digest/SKILL.md +53 -31
- package/plugin/skills/video-digest/templates/visual.md +22 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diagram-schema.d.ts","sourceRoot":"","sources":["../../../src/digest/diagram-schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,UAAU,EAAc,MAAM,kBAAkB,CAAC;AAE1D;;;GAGG;AAEH,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;IAClC,QAAQ,CAAC,EAAE,WAAW,EAAE,CAAC;CAC1B;AAED,2DAA2D;AAC3D,eAAO,MAAM,iBAAiB,EAAE,CAAC,CAAC,OAAO,CAAC,WAAW,CAMpD,CAAC;AAQF,eAAO,MAAM,aAAa,8FAShB,CAAC;AAEX;;;GAGG;AACH,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IA2CxB,CAAC;AAEH,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AACpD,MAAM,MAAM,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { citeSchema, textSchema } from './schema-base.js';
|
|
3
|
+
/** 깊이와 노드 수 상한은 lint가 경로와 함께 잡는다. 그림이 화면을 넘지 않게 하려는 것이다 */
|
|
4
|
+
export const mindmapNodeSchema = z.lazy(() => z.object({
|
|
5
|
+
label: textSchema,
|
|
6
|
+
cite: citeSchema.optional(),
|
|
7
|
+
children: z.array(mindmapNodeSchema).max(12).optional(),
|
|
8
|
+
}));
|
|
9
|
+
const diagramNodeSchema = z.object({ label: textSchema, cite: citeSchema.optional() });
|
|
10
|
+
const diagramCaption = textSchema.optional();
|
|
11
|
+
const nodes = (min, max) => z.array(diagramNodeSchema).min(min).max(max);
|
|
12
|
+
const axisSchema = z.object({ low: textSchema, high: textSchema });
|
|
13
|
+
const sideSchema = z.object({ title: textSchema.optional(), items: nodes(1, 6) });
|
|
14
|
+
export const DIAGRAM_KINDS = [
|
|
15
|
+
'flow',
|
|
16
|
+
'cycle',
|
|
17
|
+
'compare',
|
|
18
|
+
'tree',
|
|
19
|
+
'timeline',
|
|
20
|
+
'matrix',
|
|
21
|
+
'causal',
|
|
22
|
+
'beforeAfter',
|
|
23
|
+
];
|
|
24
|
+
/**
|
|
25
|
+
* 도식은 정해진 종류의 구조만 받고 그림은 svg.ts가 그린다.
|
|
26
|
+
* 모델이 쓴 SVG를 그대로 실으면 스크립트나 외부 링크가 섞일 수 있어서 원문은 받지 않는다
|
|
27
|
+
*/
|
|
28
|
+
export const diagramSchema = z.discriminatedUnion('kind', [
|
|
29
|
+
z.object({ kind: z.literal('flow'), caption: diagramCaption, steps: nodes(2, 8) }),
|
|
30
|
+
z.object({ kind: z.literal('cycle'), caption: diagramCaption, steps: nodes(3, 8) }),
|
|
31
|
+
z.object({
|
|
32
|
+
kind: z.literal('compare'),
|
|
33
|
+
caption: diagramCaption,
|
|
34
|
+
columns: z
|
|
35
|
+
.array(z.object({ title: textSchema, items: nodes(1, 6) }))
|
|
36
|
+
.min(2)
|
|
37
|
+
.max(4),
|
|
38
|
+
}),
|
|
39
|
+
z.object({ kind: z.literal('tree'), caption: diagramCaption, root: mindmapNodeSchema }),
|
|
40
|
+
/** when은 영상 속 시점("1998년", "2단계")이다. 재생 시각은 cite가 이미 갖고 있다 */
|
|
41
|
+
z.object({
|
|
42
|
+
kind: z.literal('timeline'),
|
|
43
|
+
caption: diagramCaption,
|
|
44
|
+
events: z
|
|
45
|
+
.array(z.object({ when: textSchema, label: textSchema, cite: citeSchema.optional() }))
|
|
46
|
+
.min(2)
|
|
47
|
+
.max(10),
|
|
48
|
+
}),
|
|
49
|
+
/** cells는 왼쪽 위, 오른쪽 위, 왼쪽 아래, 오른쪽 아래 순서다. 위가 y.high, 오른쪽이 x.high다 */
|
|
50
|
+
z.object({
|
|
51
|
+
kind: z.literal('matrix'),
|
|
52
|
+
caption: diagramCaption,
|
|
53
|
+
x: axisSchema,
|
|
54
|
+
y: axisSchema,
|
|
55
|
+
cells: z.array(z.array(diagramNodeSchema).max(4)).length(4),
|
|
56
|
+
}),
|
|
57
|
+
// 둘 다 비면 그림이 상자 하나라서 lint가 DIAGRAM_EMPTY로 막는다. 어느 쪽이 빈지 경로로 알리려고 스키마에서 안 잡는다
|
|
58
|
+
z.object({
|
|
59
|
+
kind: z.literal('causal'),
|
|
60
|
+
caption: diagramCaption,
|
|
61
|
+
inputs: z.array(diagramNodeSchema).max(6).default([]),
|
|
62
|
+
center: diagramNodeSchema,
|
|
63
|
+
outputs: z.array(diagramNodeSchema).max(6).default([]),
|
|
64
|
+
}),
|
|
65
|
+
z.object({
|
|
66
|
+
kind: z.literal('beforeAfter'),
|
|
67
|
+
caption: diagramCaption,
|
|
68
|
+
before: sideSchema,
|
|
69
|
+
after: sideSchema,
|
|
70
|
+
}),
|
|
71
|
+
]);
|
|
72
|
+
//# sourceMappingURL=diagram-schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diagram-schema.js","sourceRoot":"","sources":["../../../src/digest/diagram-schema.ts"],"names":[],"mappings":"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"}
|
|
@@ -13,13 +13,33 @@ export interface FramesResult {
|
|
|
13
13
|
total: number;
|
|
14
14
|
/** ffmpeg가 오류 없이 끝났는데 파일이 안 생긴 frameSec. 영상 끝을 넘긴 시각이 주로 여기 온다 */
|
|
15
15
|
missing: number[];
|
|
16
|
+
shots: ShotFramesResult;
|
|
17
|
+
}
|
|
18
|
+
export interface ShotFramesResult {
|
|
19
|
+
total: number;
|
|
20
|
+
extracted: number;
|
|
21
|
+
skipped: number;
|
|
22
|
+
/** 720p 영상에서 뽑은 장 수. 나머지는 프레임용 저화질 영상에서 뽑았다 */
|
|
23
|
+
hires: number;
|
|
24
|
+
missing: number[];
|
|
25
|
+
/** 720p로 못 뽑은 장이 있을 때 첫 사유. 못 받았는지 받고도 못 읽었는지 앞머리로 가른다 */
|
|
26
|
+
hiresFailure?: string;
|
|
16
27
|
}
|
|
17
28
|
/** 프레임 파일 이름. 렌더러가 frameSec으로 같은 이름을 찾는다 */
|
|
18
29
|
export declare function frameFileName(sec: number): string;
|
|
30
|
+
/** shots 장면 파일 이름. 소섹션 프레임과 화질이 달라 이름을 따로 둔다 */
|
|
31
|
+
export declare function shotFileName(sec: number): string;
|
|
32
|
+
/** 소섹션 shots의 시각. 소섹션 프레임과 같은 시각이어도 720p로 따로 뽑는다 */
|
|
33
|
+
export declare function collectShotSecs(digest: Digest): number[];
|
|
19
34
|
export declare function collectFrameSecs(digest: Digest): number[];
|
|
35
|
+
/**
|
|
36
|
+
* ffmpeg showinfo 로그에서 고른 프레임의 시각을 뽑는다.
|
|
37
|
+
* 입력 메타데이터 줄에도 pts_time:이 들어올 수 있어 showinfo 필터가 프레임마다 찍는 n: 줄만 읽는다
|
|
38
|
+
*/
|
|
39
|
+
export declare function parseShowinfo(log: string): number[];
|
|
20
40
|
export declare function ensureVideo(run: RunCommand, transcript: Transcript, cacheDir: string, cwd: string): Promise<string>;
|
|
21
41
|
/**
|
|
22
|
-
* digest.json의 소섹션 frameSec마다 한 장씩 뽑아 frames/에 둔다.
|
|
42
|
+
* digest.json의 소섹션 frameSec마다 한 장씩 뽑아 frames/에 둔다. shots 장면도 720p로 함께 뽑는다.
|
|
23
43
|
* 요약이 끝나야 시각이 정해져서 ingest와 따로 돈다.
|
|
24
44
|
*/
|
|
25
45
|
export declare function extractFrames(slug: string, opts: FramesOptions): Promise<FramesResult>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"frames.d.ts","sourceRoot":"","sources":["../../../src/digest/frames.ts"],"names":[],"mappings":"AAIA,OAAO,EAAmB,KAAK,MAAM,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AAE5E,OAAO,EAAiC,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAE5E,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,0BAA0B;IAC1B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,GAAG,CAAC,EAAE,UAAU,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,kEAAkE;IAClE,OAAO,EAAE,MAAM,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"frames.d.ts","sourceRoot":"","sources":["../../../src/digest/frames.ts"],"names":[],"mappings":"AAIA,OAAO,EAAmB,KAAK,MAAM,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AAE5E,OAAO,EAAiC,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAE5E,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,0BAA0B;IAC1B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,GAAG,CAAC,EAAE,UAAU,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,kEAAkE;IAClE,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,KAAK,EAAE,gBAAgB,CAAC;CACzB;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,+CAA+C;IAC/C,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,0DAA0D;IAC1D,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,4CAA4C;AAC5C,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED,gDAAgD;AAChD,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEhD;AAED,oDAAoD;AACpD,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAKxD;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAKzD;AAsDD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAMnD;AAED,wBAAsB,WAAW,CAC/B,GAAG,EAAE,UAAU,EACf,UAAU,EAAE,UAAU,EACtB,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,MAAM,CAAC,CAOjB;AA8BD;;;GAGG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC,CAqG5F"}
|
|
@@ -9,6 +9,15 @@ import { runCommand, timeoutFor, ytdlp } from './ytdlp.js';
|
|
|
9
9
|
export function frameFileName(sec) {
|
|
10
10
|
return `t${String(Math.round(sec * 1000)).padStart(8, '0')}.jpg`;
|
|
11
11
|
}
|
|
12
|
+
/** shots 장면 파일 이름. 소섹션 프레임과 화질이 달라 이름을 따로 둔다 */
|
|
13
|
+
export function shotFileName(sec) {
|
|
14
|
+
return `s${String(Math.round(sec * 1000)).padStart(8, '0')}.jpg`;
|
|
15
|
+
}
|
|
16
|
+
/** 소섹션 shots의 시각. 소섹션 프레임과 같은 시각이어도 720p로 따로 뽑는다 */
|
|
17
|
+
export function collectShotSecs(digest) {
|
|
18
|
+
const secs = digest.reports.flatMap((r) => r.sections.flatMap((s) => s.subsections.flatMap((sub) => (sub.shots ?? []).map((x) => x.sec))));
|
|
19
|
+
return [...new Set(secs)].sort((a, b) => a - b);
|
|
20
|
+
}
|
|
12
21
|
export function collectFrameSecs(digest) {
|
|
13
22
|
const secs = digest.reports.flatMap((r) => r.sections.flatMap((s) => s.subsections.map((sub) => sub.frameSec)));
|
|
14
23
|
return [...new Set(secs)].sort((a, b) => a - b);
|
|
@@ -19,40 +28,82 @@ export function collectFrameSecs(digest) {
|
|
|
19
28
|
*/
|
|
20
29
|
const FORMAT = 'bv*[height<=480][ext=mp4]/b[height<=480][ext=mp4]/18';
|
|
21
30
|
const FORMAT_RETRY = '135/134/136/18';
|
|
31
|
+
/**
|
|
32
|
+
* shots는 슬라이드 글자가 읽혀야 해서 720p 영상을 따로 받는다. 디코딩이 가벼운 H.264를 먼저 고른다.
|
|
33
|
+
* 스트림 주소만 받아 ffmpeg로 그 시각을 읽으면 유튜브가 403을 내서 영상 전체를 받는다
|
|
34
|
+
*/
|
|
35
|
+
const HIRES_FORMAT = 'bv*[height<=720][height>=600][vcodec^=avc1]/bv*[height<=720][height>=600][ext=mp4]/136/22';
|
|
36
|
+
const HIRES_RETRY = '136/22';
|
|
37
|
+
const LOWRES_BASE = 'video';
|
|
38
|
+
const HIRES_BASE = 'video-720';
|
|
22
39
|
// yt-dlp가 중단되면 video.mp4.part나 video.mp4.ytdl 같은 보조 파일이 남는다. 완성본 확장자만 캐시로 본다
|
|
23
|
-
const
|
|
24
|
-
function findCachedVideo(cacheDir) {
|
|
40
|
+
const VIDEO_EXT = /\.(mp4|webm|mkv)$/;
|
|
41
|
+
function findCachedVideo(cacheDir, base) {
|
|
25
42
|
if (!existsSync(cacheDir))
|
|
26
43
|
return null;
|
|
27
|
-
const hit = readdirSync(cacheDir).find((f) =>
|
|
44
|
+
const hit = readdirSync(cacheDir).find((f) => VIDEO_EXT.test(f) && f.slice(0, f.lastIndexOf('.')) === base);
|
|
28
45
|
return hit ? join(cacheDir, hit) : null;
|
|
29
46
|
}
|
|
30
|
-
|
|
47
|
+
async function downloadVideo(run, transcript, cacheDir, cwd, base, formats) {
|
|
31
48
|
const { meta } = transcript;
|
|
32
|
-
|
|
33
|
-
if (!meta.url)
|
|
34
|
-
throw new Error('transcript에 원본 영상 경로가 없어요.');
|
|
35
|
-
return fileURLToPath(meta.url);
|
|
36
|
-
}
|
|
37
|
-
const cached = findCachedVideo(cacheDir);
|
|
49
|
+
const cached = findCachedVideo(cacheDir, base);
|
|
38
50
|
if (cached)
|
|
39
51
|
return cached;
|
|
40
52
|
if (!meta.videoId)
|
|
41
53
|
throw new Error('transcript에 videoId가 없어요.');
|
|
42
54
|
mkdirSync(cacheDir, { recursive: true });
|
|
43
55
|
// url 필드 대신 검증된 videoId로 주소를 다시 만들고 '--' 뒤에 둬서 옵션으로 읽히지 않게 한다
|
|
44
|
-
const out = ['-o', join(cacheDir,
|
|
45
|
-
await ytdlp(run, ['-f',
|
|
56
|
+
const out = ['-o', join(cacheDir, `${base}.%(ext)s`), '--', youtubeWatchUrl(meta.videoId)];
|
|
57
|
+
await ytdlp(run, ['-f', formats[0], ...out], ['-f', formats[1], ...out], {
|
|
46
58
|
cwd,
|
|
47
59
|
timeoutMs: timeoutFor(meta.durationSec, 1),
|
|
48
60
|
});
|
|
49
|
-
const got = findCachedVideo(cacheDir);
|
|
61
|
+
const got = findCachedVideo(cacheDir, base);
|
|
50
62
|
if (!got)
|
|
51
63
|
throw new Error('영상을 받지 못했어요.');
|
|
52
64
|
return got;
|
|
53
65
|
}
|
|
54
66
|
/**
|
|
55
|
-
*
|
|
67
|
+
* ffmpeg showinfo 로그에서 고른 프레임의 시각을 뽑는다.
|
|
68
|
+
* 입력 메타데이터 줄에도 pts_time:이 들어올 수 있어 showinfo 필터가 프레임마다 찍는 n: 줄만 읽는다
|
|
69
|
+
*/
|
|
70
|
+
export function parseShowinfo(log) {
|
|
71
|
+
return [
|
|
72
|
+
...log.matchAll(/^\[Parsed_showinfo_\d+ @ [^\]]+\] +n: *\d+ [^\n]*?\bpts_time:\s*(\d+(?:\.\d+)?)/gm),
|
|
73
|
+
].map((m) => Number(m[1]));
|
|
74
|
+
}
|
|
75
|
+
export async function ensureVideo(run, transcript, cacheDir, cwd) {
|
|
76
|
+
const { meta } = transcript;
|
|
77
|
+
if (meta.source === 'file') {
|
|
78
|
+
if (!meta.url)
|
|
79
|
+
throw new Error('transcript에 원본 영상 경로가 없어요.');
|
|
80
|
+
return fileURLToPath(meta.url);
|
|
81
|
+
}
|
|
82
|
+
return downloadVideo(run, transcript, cacheDir, cwd, LOWRES_BASE, [FORMAT, FORMAT_RETRY]);
|
|
83
|
+
}
|
|
84
|
+
const firstLine = (e) => (e instanceof Error ? e.message : String(e)).split('\n')[0].slice(0, 200);
|
|
85
|
+
/**
|
|
86
|
+
* shots 장면을 읽을 영상. 로컬 파일이면 원본을 그대로 쓰고 유튜브면 720p를 받아 캐시에 둔다.
|
|
87
|
+
* 못 받으면 사유를 돌려주고 그때는 저화질 영상에서 뽑는다
|
|
88
|
+
*/
|
|
89
|
+
async function hiresVideo(run, transcript, cacheDir, cwd) {
|
|
90
|
+
const { meta } = transcript;
|
|
91
|
+
if (meta.source === 'file') {
|
|
92
|
+
return meta.url ? { path: fileURLToPath(meta.url) } : { failure: '원본 영상 경로가 없어요' };
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
const path = await downloadVideo(run, transcript, cacheDir, cwd, HIRES_BASE, [
|
|
96
|
+
HIRES_FORMAT,
|
|
97
|
+
HIRES_RETRY,
|
|
98
|
+
]);
|
|
99
|
+
return { path };
|
|
100
|
+
}
|
|
101
|
+
catch (e) {
|
|
102
|
+
return { failure: `720p 영상을 못 받았어요: ${firstLine(e)}` };
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* digest.json의 소섹션 frameSec마다 한 장씩 뽑아 frames/에 둔다. shots 장면도 720p로 함께 뽑는다.
|
|
56
107
|
* 요약이 끝나야 시각이 정해져서 ingest와 따로 돈다.
|
|
57
108
|
*/
|
|
58
109
|
export async function extractFrames(slug, opts) {
|
|
@@ -66,33 +117,78 @@ export async function extractFrames(slug, opts) {
|
|
|
66
117
|
if (!digest) {
|
|
67
118
|
throw new Error(`digest.json이 없거나 형식이 틀렸어요. 먼저 요약을 쓰고 validate를 통과시켜 주세요: ${slug}`);
|
|
68
119
|
}
|
|
69
|
-
const secs = collectFrameSecs(digest);
|
|
70
120
|
const framesDir = store.framesDir(slug);
|
|
71
121
|
mkdirSync(framesDir, { recursive: true });
|
|
72
|
-
const
|
|
73
|
-
const p = join(framesDir,
|
|
122
|
+
const pending = (file) => {
|
|
123
|
+
const p = join(framesDir, file);
|
|
74
124
|
return opts.force || !existsSync(p) || statSync(p).size === 0;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
|
|
125
|
+
};
|
|
126
|
+
const secs = collectFrameSecs(digest);
|
|
127
|
+
const todo = secs.filter((sec) => pending(frameFileName(sec)));
|
|
128
|
+
const shotSecs = collectShotSecs(digest);
|
|
129
|
+
const shotTodo = shotSecs.filter((sec) => pending(shotFileName(sec)));
|
|
130
|
+
const shots = {
|
|
131
|
+
total: shotSecs.length,
|
|
132
|
+
extracted: 0,
|
|
133
|
+
skipped: shotSecs.length - shotTodo.length,
|
|
134
|
+
hires: 0,
|
|
135
|
+
missing: [],
|
|
136
|
+
};
|
|
137
|
+
if (todo.length === 0 && shotTodo.length === 0) {
|
|
138
|
+
return {
|
|
139
|
+
framesDir,
|
|
140
|
+
extracted: 0,
|
|
141
|
+
skipped: secs.length,
|
|
142
|
+
total: secs.length,
|
|
143
|
+
missing: [],
|
|
144
|
+
shots,
|
|
145
|
+
};
|
|
78
146
|
}
|
|
79
147
|
// cacheDir는 레포 안이라 cwd로 쓰면 그 레포가 커밋한 yt-dlp.conf를 읽는다
|
|
80
148
|
const cwd = mkdtempSync(join(tmpdir(), 'gestalt-digest-run-'));
|
|
81
149
|
try {
|
|
82
|
-
|
|
150
|
+
let video;
|
|
151
|
+
const lowres = async () => (video ??= await ensureVideo(run, transcript, store.videoCacheDir(slug), cwd));
|
|
83
152
|
// 긴 영상일수록 뒤쪽 시각을 찾는 데 오래 걸려서 상한을 길이에 비례시킨다
|
|
84
153
|
const frameTimeout = timeoutFor(transcript.meta.durationSec, 0.05, 60);
|
|
85
|
-
const
|
|
86
|
-
for (const sec of todo) {
|
|
87
|
-
const file = join(framesDir, frameFileName(sec));
|
|
154
|
+
const grab = async (input, sec, file, width, q) => {
|
|
88
155
|
await run('ffmpeg', [
|
|
89
|
-
...['-v', 'error', '-y', '-ss', String(sec), '-i',
|
|
90
|
-
...['-frames:v', '1', '-vf',
|
|
156
|
+
...['-v', 'error', '-y', '-ss', String(sec), '-i', input],
|
|
157
|
+
...['-frames:v', '1', '-vf', `scale='min(${width},iw)':-2`, '-q:v', String(q)],
|
|
91
158
|
file,
|
|
92
159
|
], { cwd, timeoutMs: frameTimeout });
|
|
93
160
|
// 영상 끝을 넘긴 시각이면 ffmpeg가 파일 없이 0으로 끝나기도 한다
|
|
94
|
-
|
|
161
|
+
return existsSync(file) && statSync(file).size > 0;
|
|
162
|
+
};
|
|
163
|
+
const missing = [];
|
|
164
|
+
for (const sec of todo) {
|
|
165
|
+
if (!(await grab(await lowres(), sec, join(framesDir, frameFileName(sec)), 960, 5))) {
|
|
95
166
|
missing.push(sec);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
const hires = shotTodo.length > 0
|
|
170
|
+
? await hiresVideo(run, transcript, store.videoCacheDir(slug), cwd)
|
|
171
|
+
: null;
|
|
172
|
+
if (hires && 'failure' in hires)
|
|
173
|
+
shots.hiresFailure = hires.failure;
|
|
174
|
+
for (const sec of shotTodo) {
|
|
175
|
+
const file = join(framesDir, shotFileName(sec));
|
|
176
|
+
let ok = false;
|
|
177
|
+
if (hires && 'path' in hires) {
|
|
178
|
+
// 720p 영상을 못 읽으면 저화질 영상으로 물러난다. 흐려도 장면은 보인다
|
|
179
|
+
ok = await grab(hires.path, sec, file, 1280, 3).catch((e) => {
|
|
180
|
+
shots.hiresFailure ??= `720p 영상에서 ${sec}초 장면을 못 읽었어요: ${firstLine(e)}`;
|
|
181
|
+
return false;
|
|
182
|
+
});
|
|
183
|
+
if (ok)
|
|
184
|
+
shots.hires += 1;
|
|
185
|
+
}
|
|
186
|
+
if (!ok)
|
|
187
|
+
ok = await grab(await lowres(), sec, file, 960, 3);
|
|
188
|
+
if (ok)
|
|
189
|
+
shots.extracted += 1;
|
|
190
|
+
else
|
|
191
|
+
shots.missing.push(sec);
|
|
96
192
|
}
|
|
97
193
|
return {
|
|
98
194
|
framesDir,
|
|
@@ -100,6 +196,7 @@ export async function extractFrames(slug, opts) {
|
|
|
100
196
|
skipped: secs.length - todo.length,
|
|
101
197
|
total: secs.length,
|
|
102
198
|
missing,
|
|
199
|
+
shots,
|
|
103
200
|
};
|
|
104
201
|
}
|
|
105
202
|
finally {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"frames.js","sourceRoot":"","sources":["../../../src/digest/frames.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC5F,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,eAAe,EAAgC,MAAM,aAAa,CAAC;AAC5E,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,KAAK,EAAmB,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"frames.js","sourceRoot":"","sources":["../../../src/digest/frames.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/digest/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/digest/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC"}
|
package/dist/src/digest/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/digest/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/digest/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC"}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { type Block, type Extension, type MindmapNode } from './blocks.js';
|
|
2
2
|
import type { DigestIssue } from './lint.js';
|
|
3
|
-
import { type Cite, type Transcript } from './schema.js';
|
|
3
|
+
import { type Cite, type Diagram, type Transcript } from './schema.js';
|
|
4
4
|
import type { DigestTemplate } from './templates.js';
|
|
5
5
|
/**
|
|
6
6
|
* 확장 리포트의 문자열 한 칸.
|
|
7
|
-
* title은 탭과 소제목처럼 이름으로 쓰여
|
|
7
|
+
* title은 탭과 소제목처럼 이름으로 쓰여 본문 윤문에서 빠진다. 그림 안 글자는 prose.ts가 라벨로 따로 모은다. verbatim은 대사나 댓글 원문이라 손대지 않는다
|
|
8
8
|
*/
|
|
9
9
|
export interface ExtensionText {
|
|
10
10
|
path: string;
|
|
@@ -34,6 +34,13 @@ export declare function walkMindmap(node: MindmapNode, path: string, depth?: num
|
|
|
34
34
|
path: string;
|
|
35
35
|
depth: number;
|
|
36
36
|
}>;
|
|
37
|
+
/** 도식에서 라벨 칸을 경로와 함께 돈다. 열 제목과 축 이름, 시점 글자는 출처가 없어서 cite도 없다 */
|
|
38
|
+
export declare function walkDiagram(d: Diagram, prefix: string): Generator<{
|
|
39
|
+
label: string;
|
|
40
|
+
cite?: Cite;
|
|
41
|
+
path: string;
|
|
42
|
+
labelPath: string;
|
|
43
|
+
}>;
|
|
37
44
|
/** 블록 하나에서 글자와 출처를 경로와 함께 뽑는다. lint와 윤문이 같은 경로를 쓴다 */
|
|
38
45
|
export declare function blockFields(block: Block, p: string): {
|
|
39
46
|
texts: ExtensionText[];
|
|
@@ -53,5 +60,12 @@ export interface TreeLimit {
|
|
|
53
60
|
}
|
|
54
61
|
/** 마인드맵과 대섹션 도식 트리가 같은 깊이 규칙을 쓴다. 노드 상한과 코드만 다르다 */
|
|
55
62
|
export declare function checkTree(root: MindmapNode, rootPath: string, limit: TreeLimit, issues: DigestIssue[]): void;
|
|
63
|
+
/** 대섹션 도식의 트리는 마인드맵보다 작게 둔다. 본문 사이에 끼는 그림이라서다 */
|
|
64
|
+
export declare const DIAGRAM_TREE_MAX_NODES = 30;
|
|
65
|
+
/**
|
|
66
|
+
* 스키마로는 못 잡는 도식 모양을 본다. 트리 깊이와 크기, 상자 하나만 남는 인과도와 빈 매트릭스다.
|
|
67
|
+
* 확장 리포트의 figure 블록도 같은 검사를 쓴다
|
|
68
|
+
*/
|
|
69
|
+
export declare function checkDiagramShape(d: Diagram, dp: string, issues: DigestIssue[]): void;
|
|
56
70
|
export declare function lintExtensions(extensions: Extension[], transcript: Transcript, issues: DigestIssue[], opts?: ExtensionLintOptions): void;
|
|
57
71
|
//# sourceMappingURL=lint-extension.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lint-extension.d.ts","sourceRoot":"","sources":["../../../src/digest/lint-extension.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,KAAK,EACV,KAAK,SAAS,EAEd,KAAK,WAAW,EACjB,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAA+B,KAAK,IAAI,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"lint-extension.d.ts","sourceRoot":"","sources":["../../../src/digest/lint-extension.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,KAAK,EACV,KAAK,SAAS,EAEd,KAAK,WAAW,EACjB,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAA+B,KAAK,IAAI,EAAE,KAAK,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AACpG,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAErD;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,UAAU,CAAC;CACrC;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,IAAI,CAAC;CACZ;AAED,MAAM,WAAW,oBAAoB;IACnC,+CAA+C;IAC/C,SAAS,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,IAAI,CAAC,cAAc,EAAE,IAAI,GAAG,QAAQ,CAAC,CAAC,CAAC;IACvE;;;OAGG;IACH,QAAQ,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;IAC9C,+CAA+C;IAC/C,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,6CAA6C;AAC7C,eAAO,MAAM,iBAAiB,IAAI,CAAC;AACnC,eAAO,MAAM,iBAAiB,KAAK,CAAC;AAcpC,wBAAiB,WAAW,CAC1B,IAAI,EAAE,WAAW,EACjB,IAAI,EAAE,MAAM,EACZ,KAAK,SAAI,GACR,SAAS,CAAC;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAK/D;AAED,gEAAgE;AAChE,wBAAiB,WAAW,CAC1B,CAAC,EAAE,OAAO,EACV,MAAM,EAAE,MAAM,GACb,SAAS,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,CA0D5E;AAED,sDAAsD;AACtD,wBAAgB,WAAW,CACzB,KAAK,EAAE,KAAK,EACZ,CAAC,EAAE,MAAM,GACR;IAAE,KAAK,EAAE,aAAa,EAAE,CAAC;IAAC,KAAK,EAAE,aAAa,EAAE,CAAA;CAAE,CA6GpD;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,SAAS,EAAE,CAAC,EAAE,MAAM;;;EASxD;AAsHD,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,qDAAqD;IACrD,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAED,oDAAoD;AACpD,wBAAgB,SAAS,CACvB,IAAI,EAAE,WAAW,EACjB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,SAAS,EAChB,MAAM,EAAE,WAAW,EAAE,GACpB,IAAI,CAqBN;AAED,iDAAiD;AACjD,eAAO,MAAM,sBAAsB,KAAK,CAAC;AAUzC;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,GAAG,IAAI,CAmBrF;AAkHD,wBAAgB,cAAc,CAC5B,UAAU,EAAE,SAAS,EAAE,EACvB,UAAU,EAAE,UAAU,EACtB,MAAM,EAAE,WAAW,EAAE,EACrB,IAAI,GAAE,oBAAyB,GAC9B,IAAI,CA2BN"}
|
|
@@ -17,6 +17,74 @@ export function* walkMindmap(node, path, depth = 0) {
|
|
|
17
17
|
yield* walkMindmap(child, `${path}.children.${k}`, depth + 1);
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
|
+
/** 도식에서 라벨 칸을 경로와 함께 돈다. 열 제목과 축 이름, 시점 글자는 출처가 없어서 cite도 없다 */
|
|
21
|
+
export function* walkDiagram(d, prefix) {
|
|
22
|
+
const node = (n, path) => ({
|
|
23
|
+
label: n.label,
|
|
24
|
+
cite: n.cite,
|
|
25
|
+
path,
|
|
26
|
+
labelPath: `${path}.label`,
|
|
27
|
+
});
|
|
28
|
+
switch (d.kind) {
|
|
29
|
+
case 'flow':
|
|
30
|
+
case 'cycle':
|
|
31
|
+
for (const [k, step] of d.steps.entries())
|
|
32
|
+
yield node(step, `${prefix}.steps.${k}`);
|
|
33
|
+
return;
|
|
34
|
+
case 'compare':
|
|
35
|
+
for (const [c, col] of d.columns.entries()) {
|
|
36
|
+
const cp = `${prefix}.columns.${c}`;
|
|
37
|
+
yield { label: col.title, path: cp, labelPath: `${cp}.title` };
|
|
38
|
+
for (const [k, item] of col.items.entries())
|
|
39
|
+
yield node(item, `${cp}.items.${k}`);
|
|
40
|
+
}
|
|
41
|
+
return;
|
|
42
|
+
case 'tree':
|
|
43
|
+
for (const w of walkMindmap(d.root, `${prefix}.root`))
|
|
44
|
+
yield node(w.node, w.path);
|
|
45
|
+
return;
|
|
46
|
+
case 'timeline':
|
|
47
|
+
for (const [k, ev] of d.events.entries()) {
|
|
48
|
+
const ep = `${prefix}.events.${k}`;
|
|
49
|
+
yield { label: ev.when, path: ep, labelPath: `${ep}.when` };
|
|
50
|
+
yield node(ev, ep);
|
|
51
|
+
}
|
|
52
|
+
return;
|
|
53
|
+
case 'matrix':
|
|
54
|
+
for (const axis of ['x', 'y']) {
|
|
55
|
+
for (const end of ['low', 'high']) {
|
|
56
|
+
const ap = `${prefix}.${axis}.${end}`;
|
|
57
|
+
yield { label: d[axis][end], path: ap, labelPath: ap };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
for (const [c, cell] of d.cells.entries()) {
|
|
61
|
+
for (const [k, item] of cell.entries())
|
|
62
|
+
yield node(item, `${prefix}.cells.${c}.${k}`);
|
|
63
|
+
}
|
|
64
|
+
return;
|
|
65
|
+
case 'causal':
|
|
66
|
+
for (const [k, n] of d.inputs.entries())
|
|
67
|
+
yield node(n, `${prefix}.inputs.${k}`);
|
|
68
|
+
yield node(d.center, `${prefix}.center`);
|
|
69
|
+
for (const [k, n] of d.outputs.entries())
|
|
70
|
+
yield node(n, `${prefix}.outputs.${k}`);
|
|
71
|
+
return;
|
|
72
|
+
case 'beforeAfter':
|
|
73
|
+
for (const side of ['before', 'after']) {
|
|
74
|
+
const sp = `${prefix}.${side}`;
|
|
75
|
+
const { title, items } = d[side];
|
|
76
|
+
if (title)
|
|
77
|
+
yield { label: title, path: sp, labelPath: `${sp}.title` };
|
|
78
|
+
for (const [k, item] of items.entries())
|
|
79
|
+
yield node(item, `${sp}.items.${k}`);
|
|
80
|
+
}
|
|
81
|
+
return;
|
|
82
|
+
default: {
|
|
83
|
+
const never = d;
|
|
84
|
+
throw new Error(`알 수 없는 도식: ${String(never)}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
20
88
|
/** 블록 하나에서 글자와 출처를 경로와 함께 뽑는다. lint와 윤문이 같은 경로를 쓴다 */
|
|
21
89
|
export function blockFields(block, p) {
|
|
22
90
|
const texts = [];
|
|
@@ -103,6 +171,20 @@ export function blockFields(block, p) {
|
|
|
103
171
|
cite(`${path}.cite`, node.cite);
|
|
104
172
|
}
|
|
105
173
|
break;
|
|
174
|
+
case 'figure':
|
|
175
|
+
texts.push({ path: `${p}.title`, text: block.title, role: 'title' });
|
|
176
|
+
if (block.diagram.caption) {
|
|
177
|
+
texts.push({ path: `${p}.diagram.caption`, text: block.diagram.caption, role: 'title' });
|
|
178
|
+
}
|
|
179
|
+
// 도식 라벨도 그림 안 글자라 마인드맵처럼 이름 칸으로 둔다
|
|
180
|
+
for (const node of walkDiagram(block.diagram, `${p}.diagram`)) {
|
|
181
|
+
texts.push({ path: node.labelPath, text: node.label, role: 'title' });
|
|
182
|
+
if (node.cite)
|
|
183
|
+
cite(`${node.path}.cite`, node.cite);
|
|
184
|
+
}
|
|
185
|
+
texts.push({ path: `${p}.explanation`, text: block.explanation, role: 'body' });
|
|
186
|
+
cite(`${p}.cite`, block.cite);
|
|
187
|
+
break;
|
|
106
188
|
case 'comments':
|
|
107
189
|
texts.push({ path: `${p}.overview`, text: block.overview, role: 'body' });
|
|
108
190
|
block.groups.forEach((g, k) => {
|
|
@@ -261,6 +343,39 @@ export function checkTree(root, rootPath, limit, issues) {
|
|
|
261
343
|
});
|
|
262
344
|
}
|
|
263
345
|
}
|
|
346
|
+
/** 대섹션 도식의 트리는 마인드맵보다 작게 둔다. 본문 사이에 끼는 그림이라서다 */
|
|
347
|
+
export const DIAGRAM_TREE_MAX_NODES = 30;
|
|
348
|
+
const DIAGRAM_TREE_LIMIT = {
|
|
349
|
+
maxNodes: DIAGRAM_TREE_MAX_NODES,
|
|
350
|
+
depthCode: 'DIAGRAM_DEPTH',
|
|
351
|
+
sizeCode: 'DIAGRAM_SIZE',
|
|
352
|
+
name: '도식 트리',
|
|
353
|
+
topic: '도식 트리는',
|
|
354
|
+
};
|
|
355
|
+
/**
|
|
356
|
+
* 스키마로는 못 잡는 도식 모양을 본다. 트리 깊이와 크기, 상자 하나만 남는 인과도와 빈 매트릭스다.
|
|
357
|
+
* 확장 리포트의 figure 블록도 같은 검사를 쓴다
|
|
358
|
+
*/
|
|
359
|
+
export function checkDiagramShape(d, dp, issues) {
|
|
360
|
+
if (d.kind === 'tree')
|
|
361
|
+
checkTree(d.root, `${dp}.root`, DIAGRAM_TREE_LIMIT, issues);
|
|
362
|
+
if (d.kind === 'causal' && d.inputs.length === 0 && d.outputs.length === 0) {
|
|
363
|
+
issues.push({
|
|
364
|
+
path: dp,
|
|
365
|
+
severity: 'error',
|
|
366
|
+
code: 'DIAGRAM_EMPTY',
|
|
367
|
+
message: '인과도는 inputs와 outputs 중 하나는 채운다. 원인도 결과도 없으면 상자 하나만 남는다',
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
if (d.kind === 'matrix' && d.cells.every((c) => c.length === 0)) {
|
|
371
|
+
issues.push({
|
|
372
|
+
path: `${dp}.cells`,
|
|
373
|
+
severity: 'error',
|
|
374
|
+
code: 'DIAGRAM_EMPTY',
|
|
375
|
+
message: '매트릭스 네 칸이 다 비었다. 칸마다 노드를 0~4개 둔다',
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
}
|
|
264
379
|
const MINDMAP_LIMIT = {
|
|
265
380
|
maxNodes: MINDMAP_MAX_NODES,
|
|
266
381
|
depthCode: 'MINDMAP_DEPTH',
|
|
@@ -268,11 +383,13 @@ const MINDMAP_LIMIT = {
|
|
|
268
383
|
name: '마인드맵',
|
|
269
384
|
topic: '마인드맵은',
|
|
270
385
|
};
|
|
271
|
-
function
|
|
386
|
+
function checkShapes(ext, p, issues) {
|
|
272
387
|
ext.blocks.forEach((block, bi) => {
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
388
|
+
const bp = `${p}.blocks.${bi}`;
|
|
389
|
+
if (block.type === 'mindmap')
|
|
390
|
+
checkTree(block.root, `${bp}.root`, MINDMAP_LIMIT, issues);
|
|
391
|
+
if (block.type === 'figure')
|
|
392
|
+
checkDiagramShape(block.diagram, `${bp}.diagram`, issues);
|
|
276
393
|
});
|
|
277
394
|
}
|
|
278
395
|
// 정규화하면 빈 문자열이 되는 이모지 댓글이나 한두 글자 조각은 부분 일치로 넘기면 어느 원문에나 맞는다
|
|
@@ -387,7 +504,7 @@ export function lintExtensions(extensions, transcript, issues, opts = {}) {
|
|
|
387
504
|
checkListDepth(ext, p, issues);
|
|
388
505
|
checkQuotes(ext, p, transcript, issues);
|
|
389
506
|
checkQuiz(ext, p, issues);
|
|
390
|
-
|
|
507
|
+
checkShapes(ext, p, issues);
|
|
391
508
|
checkComments(ext, p, opts.comments, opts.strictVerbatim ?? false, issues);
|
|
392
509
|
checkTemplate(ext, p, opts.templates, issues);
|
|
393
510
|
});
|