@miraiclip/guides 0.1.0 → 0.1.2

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/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 comaniacs
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 comaniacs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,21 +1,21 @@
1
- # @miraiclip/guides
2
-
3
- A written, step-by-step guide from the same project as your video: one recording, two deliverables.
4
-
5
- ```ts
6
- import { buildGuide, addStills, toMarkdown, toHtml } from "@miraiclip/guides";
7
-
8
- let guide = buildGuide(doc, { title: "Turn on dark mode", session, clipId: "screen", script });
9
- guide = await addStills(guide, doc, (d, o) => renderSession.renderStill(d, o), {
10
- store: async (image, step) => save(`images/step-${step.number}.png`, image.data),
11
- });
12
- await save("guide.md", toMarkdown(guide));
13
- await save("guide.html", toHtml(guide));
14
- ```
15
-
16
- - **Steps** come from step markers (or every click when there are none). Titles come from the marker or the action ("Click “Save”", "Enter email address", "Press Enter").
17
- - **Text** comes from the narration script, or from what was said (fillers dropped).
18
- - **Screenshots** come from any still renderer, taken just before each action, with the control outlined, the rest dimmed and the step number beside it. Sensitive fields are never highlighted.
19
- - **Output** is plain files: Markdown, a standalone HTML page (light/dark) or a fragment, or JSON.
20
-
21
- Docs: https://docs.miraiclip.com/
1
+ # @miraiclip/guides
2
+
3
+ A written, step-by-step guide from the same project as your video: one recording, two deliverables.
4
+
5
+ ```ts
6
+ import { buildGuide, addStills, toMarkdown, toHtml } from "@miraiclip/guides";
7
+
8
+ let guide = buildGuide(doc, { title: "Turn on dark mode", session, clipId: "screen", script });
9
+ guide = await addStills(guide, doc, (d, o) => renderSession.renderStill(d, o), {
10
+ store: async (image, step) => save(`images/step-${step.number}.png`, image.data),
11
+ });
12
+ await save("guide.md", toMarkdown(guide));
13
+ await save("guide.html", toHtml(guide));
14
+ ```
15
+
16
+ - **Steps** come from step markers (or every click when there are none). Titles come from the marker or the action ("Click “Save”", "Enter email address", "Press Enter").
17
+ - **Text** comes from the narration script, or from what was said (fillers dropped).
18
+ - **Screenshots** come from any still renderer, taken just before each action, with the control outlined, the rest dimmed and the step number beside it. Sensitive fields are never highlighted.
19
+ - **Output** is plain files: Markdown, a standalone HTML page (light/dark) or a fragment, or JSON.
20
+
21
+ Docs: https://docs.miraiclip.com/
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/build.ts","../src/stills.ts","../src/format.ts"],"sourcesContent":["export { buildGuide, titleForAction } from \"./build.js\";\nexport type { BuildGuideOptions, Guide, GuideImage, GuideStep } from \"./build.js\";\nexport { addStills, annotationTemplate } from \"./stills.js\";\nexport type { AddStillsOptions, AnnotationStyle, StillRenderer } from \"./stills.js\";\nexport { toHtml, toJson, toMarkdown } from \"./format.js\";\nexport type { HtmlOptions, MarkdownOptions } from \"./format.js\";\n","/**\n * A written guide from the same project as the video: one step per marker\n * (or per click), the step's text from the narration script or what was\n * said, and where the action happened on screen, for an annotated\n * screenshot. Pure: no rendering, no network.\n */\nimport { listMarkers, type ProjectDocument, type VideoClip } from \"@miraiclip/core\";\nimport type { CaptureEvent, CaptureSession, CaptureTarget } from \"@miraiclip/capture\";\nimport { framePointToCanvas, rectToFrame, screenClip, sessionToClipUs } from \"@miraiclip/screencast\";\nimport { detectFillers, transcriptOnTimeline, type Transcript } from \"@miraiclip/transcript\";\n\nexport interface GuideImage {\n src: string;\n width: number;\n height: number;\n alt: string;\n}\n\nexport interface GuideStep {\n id: string;\n /** 1-based. */\n number: number;\n title: string;\n /** Paragraph text (plain). */\n body?: string;\n /** Timeline time to screenshot. */\n timeUs: number;\n /** Where the action is, as fractions of the frame (canvas), at `timeUs`. */\n highlight?: { x: number; y: number; width: number; height: number };\n /** What the viewer does in this step, when the log knows. */\n action?: { kind: \"click\" | \"type\" | \"key\" | \"navigate\"; label?: string; key?: string; url?: string };\n markerId?: string;\n image?: GuideImage;\n}\n\nexport interface Guide {\n title: string;\n intro?: string;\n language?: string;\n steps: GuideStep[];\n}\n\nexport interface BuildGuideOptions {\n title: string;\n intro?: string;\n /** Steps from markers (default when the project has any) or from clicks. */\n stepsFrom?: \"markers\" | \"clicks\";\n /** Only these markers (default: all; markers with `data.kind === \"step\"` when any have it). */\n markerFilter?: (marker: { id: string; label: string; data?: Record<string, unknown> }) => boolean;\n /** The recording's interaction log and its clip: actions and highlights come from it. */\n session?: CaptureSession;\n clipId?: string;\n /**\n * Every clip of the recording, when it has been cut or split (by cleanup or\n * a voiceover's freeze frames): each action is found on the piece that\n * shows it. Overrides `clipId`.\n */\n clipIds?: string[];\n /** Step text from a narration script (by marker id or by time)… */\n script?: { language?: string; segments: { text: string; startUs: number; markerId?: string }[] };\n /** …or from what was said (fillers dropped). */\n transcript?: Transcript;\n /** Grow highlight boxes by this many CSS px of the recording (default 8). */\n highlightPaddingPx?: number;\n}\n\ntype Located = { local: number; clip: VideoClip; event: CaptureEvent; target?: CaptureTarget };\n\nfunction actionOf(e: CaptureEvent): GuideStep[\"action\"] | undefined {\n const label = \"target\" in e ? e.target?.label : undefined;\n switch (e.type) {\n case \"click\":\n case \"dblclick\":\n return { kind: \"click\", ...(label ? { label } : {}) };\n case \"input\":\n return { kind: \"type\", ...(label ? { label } : {}) };\n case \"key\":\n return e.key === \"text\" ? undefined : { kind: \"key\", key: e.key };\n case \"navigate\":\n return { kind: \"navigate\", url: e.url };\n default:\n return undefined;\n }\n}\n\n/** A step title from the action (\"Click “Save”\", \"Enter your email\", \"Press Enter\"). */\nexport function titleForAction(action: GuideStep[\"action\"]): string | undefined {\n if (!action) return undefined;\n if (action.kind === \"click\") return action.label ? `Click “${action.label}”` : undefined;\n if (action.kind === \"type\") return action.label ? (action.label.toLowerCase().startsWith(\"enter\") ? action.label : `Enter ${action.label.charAt(0).toLowerCase()}${action.label.slice(1)}`) : \"Type the value\";\n if (action.kind === \"key\") return `Press ${action.key!.replace(/\\+/g, \" + \")}`;\n return undefined;\n}\n\nexport function buildGuide(doc: ProjectDocument, options: BuildGuideOptions): Guide {\n const session = options.session;\n const clips = session ? (options.clipIds ?? (options.clipId ? [options.clipId] : [])).map((id) => screenClip(doc, id)) : [];\n const pad = options.highlightPaddingPx ?? 8;\n const located: Located[] = [];\n if (session) {\n for (const e of session.events) {\n if (![\"click\", \"dblclick\", \"input\", \"key\", \"navigate\"].includes(e.type)) continue;\n // The piece of the recording that shows this moment (none when it was cut away).\n for (const clip of clips) {\n const local = sessionToClipUs(clip, session, e.tUs);\n if (local === undefined) continue;\n located.push({ local, clip, event: e, ...(\"target\" in e && e.target ? { target: e.target } : {}) });\n break;\n }\n }\n located.sort((a, b) => a.clip.startUs + a.local - (b.clip.startUs + b.local));\n }\n const at = (l: Located) => l.clip.startUs + l.local;\n\n // The step spans.\n let markers = listMarkers(doc);\n if (markers.some((m) => m.data?.[\"kind\"] === \"step\")) markers = markers.filter((m) => m.data?.[\"kind\"] === \"step\");\n if (options.markerFilter) markers = markers.filter(options.markerFilter);\n const fromMarkers = (options.stepsFrom ?? (markers.length ? \"markers\" : \"clicks\")) === \"markers\";\n const end = clips.length ? Math.max(...clips.map((c) => c.startUs + c.durationUs)) : Math.max(0, ...Object.values(doc.clips).map((c) => c.startUs + c.durationUs));\n const spans: { startUs: number; endUs: number; markerId?: string; label?: string }[] = fromMarkers\n ? markers.map((m, i) => ({ startUs: m.timeUs, endUs: markers[i + 1]?.timeUs ?? end, markerId: m.id, label: m.label }))\n : located\n .filter((l) => l.event.type === \"click\" || l.event.type === \"dblclick\")\n .map((l, i, all) => ({ startUs: at(l), endUs: all[i + 1] ? at(all[i + 1]!) : end }));\n\n const words = options.transcript ? transcriptOnTimeline(doc, options.transcript) : [];\n const fillerIdx = new Set(options.transcript ? detectFillers(options.transcript).flatMap((s) => s.words) : []);\n const textFor = (span: (typeof spans)[number]) => {\n const fromScript = options.script?.segments.find((s) => (span.markerId && s.markerId === span.markerId) || (s.startUs >= span.startUs && s.startUs < span.endUs));\n if (fromScript) return fromScript.text;\n const said = words.filter((w) => w.timelineStartUs >= span.startUs && w.timelineStartUs < span.endUs && !fillerIdx.has(w.index)).map((w) => w.text);\n if (!said.length) return undefined;\n const text = said.join(\" \");\n return text.charAt(0).toUpperCase() + text.slice(1);\n };\n\n const steps: GuideStep[] = spans.map((span, i) => {\n // The step's main action: its first click, else its first typing, else a named key.\n const inSpan = located.filter((l) => at(l) >= span.startUs && at(l) < span.endUs);\n const main =\n inSpan.find((l) => l.event.type === \"click\" || l.event.type === \"dblclick\") ??\n inSpan.find((l) => l.event.type === \"input\") ??\n inSpan.find((l) => l.event.type === \"key\" && (l.event as { key: string }).key !== \"text\");\n const action = main ? actionOf(main.event) : undefined;\n // Screenshot just before the action (the control is visible, not yet changed); else mid-step.\n // (Kept on the action's own piece of the recording.)\n const timeUs = main ? Math.max(span.startUs, main.clip.startUs, at(main) - 150_000) : Math.round(span.startUs + Math.min(1_000_000, (span.endUs - span.startUs) / 2));\n let highlight: GuideStep[\"highlight\"];\n if (main?.target && session && !main.target.sensitive) {\n const box = rectToFrame(session, main.target.rect, pad);\n const local = timeUs - main.clip.startUs;\n const a = framePointToCanvas(doc, main.clip, local, box.x, box.y);\n const b = framePointToCanvas(doc, main.clip, local, box.x + box.width, box.y + box.height);\n highlight = { x: a.x, y: a.y, width: b.x - a.x, height: b.y - a.y };\n }\n const title = (fromMarkers && span.label) || titleForAction(action) || `Step ${i + 1}`;\n const body = textFor(span);\n return {\n id: span.markerId ? `step-${span.markerId}` : `step-${i + 1}`,\n number: i + 1,\n title,\n ...(body && body !== title ? { body } : {}),\n timeUs,\n ...(highlight ? { highlight } : {}),\n ...(action ? { action } : {}),\n ...(span.markerId ? { markerId: span.markerId } : {}),\n };\n });\n const language = options.script?.language ?? options.transcript?.language;\n return { title: options.title, ...(options.intro ? { intro: options.intro } : {}), ...(language ? { language } : {}), steps };\n}\n","/**\n * Screenshots for a guide: each step's frame rendered by whatever still\n * renderer you have (`createRenderSession().renderStill` on a server,\n * `renderProjectStill` in the browser), with the step's control outlined,\n * the rest dimmed and the step number beside it. The annotation is an html\n * clip added to a throwaway copy of the project, so the renderer needs\n * nothing new and your project is untouched.\n */\nimport { applyCommands, createProject, type ProjectDocument } from \"@miraiclip/core\";\nimport type { Guide, GuideStep } from \"./build.js\";\n\n/** Render one frame of a document to image bytes (PNG/JPEG/WebP). */\nexport type StillRenderer = (doc: ProjectDocument, options: { timeUs: number; width?: number; height?: number }) => Promise<Uint8Array | Blob>;\n\nexport interface AnnotationStyle {\n /** Outline colour (default \"#facc15\"). */\n color?: string;\n /** Darken outside the box, 0..1 (default 0.35; 0 turns it off). */\n dim?: number;\n /** Show the step number (default true). */\n number?: boolean;\n}\n\nexport interface AddStillsOptions {\n /** Save one image and return its `src` (path or URL) as the guide will reference it. */\n store: (image: { data: Uint8Array | Blob; mimeType: string }, step: GuideStep) => Promise<string>;\n /** Output size (default: the project's). */\n width?: number;\n height?: number;\n /** Outline the step's control (default true). */\n annotate?: boolean | AnnotationStyle;\n /** MIME type the renderer produces (default \"image/png\"). */\n mimeType?: string;\n}\n\n/** The overlay markup (full-frame html clip) for one step. */\nexport function annotationTemplate(): string {\n return `<div style=\"position:relative;width:100%;height:100%;overflow:hidden;font-family:Inter,system-ui,sans-serif\">\n<div style=\"position:absolute;left:{{x}}%;top:{{y}}%;width:{{w}}%;height:{{h}}%;box-sizing:border-box;border:{{stroke}}px solid {{color}};border-radius:{{radius}}px;box-shadow:0 0 0 6000px rgba(0,0,0,{{dim}})\"></div>\n<div style=\"position:absolute;left:calc({{x}}% - {{badge}}px * .5);top:calc({{y}}% - {{badge}}px * .5);width:{{badge}}px;height:{{badge}}px;border-radius:50%;background:{{color}};color:#111;display:{{showNumber}};align-items:center;justify-content:center;font-weight:700;font-size:calc({{badge}}px * .55);box-shadow:0 2px 8px rgba(0,0,0,.4)\">{{n}}</div>\n</div>`;\n}\n\nfunction annotated(doc: ProjectDocument, step: GuideStep, style: AnnotationStyle): ProjectDocument {\n if (!step.highlight) return doc;\n const p = createProject(structuredClone(doc), { historyLimit: 1 });\n const h = step.highlight;\n const unit = Math.min(doc.settings.width, doc.settings.height) / 1080;\n const fps = doc.settings.fps || 30;\n const pct = (v: number) => Math.round(v * 100_000) / 1000;\n const r = applyCommands(p, [\n { type: \"track/add\", payload: { id: \"guide-annotation\", kind: \"video\" } },\n {\n type: \"clip/add\",\n payload: {\n kind: \"html\",\n id: \"guide-annotation\",\n trackId: \"guide-annotation\",\n startUs: Math.max(0, step.timeUs - Math.round(1e6 / fps)),\n durationUs: Math.round((2 * 1e6) / fps),\n template: annotationTemplate(),\n params: {\n x: pct(h.x),\n y: pct(h.y),\n w: pct(h.width),\n h: pct(h.height),\n stroke: Math.max(2, Math.round(5 * unit)),\n radius: Math.round(12 * unit),\n badge: Math.round(44 * unit),\n color: style.color ?? \"#facc15\",\n dim: style.dim ?? 0.35,\n showNumber: style.number === false ? \"none\" : \"flex\",\n n: step.number,\n },\n },\n },\n ]);\n if (!r.ok) throw new Error(`guide annotation failed: ${JSON.stringify(r.error)}`);\n return p.getState().doc;\n}\n\n/** Render, annotate and store a screenshot for every step; returns the guide with `image` set. */\nexport async function addStills(guide: Guide, doc: ProjectDocument, render: StillRenderer, options: AddStillsOptions): Promise<Guide> {\n const style: AnnotationStyle = typeof options.annotate === \"object\" ? options.annotate : {};\n const width = options.width ?? doc.settings.width;\n const height = options.height ?? Math.round((width * doc.settings.height) / doc.settings.width);\n const steps: GuideStep[] = [];\n for (const step of guide.steps) {\n const frameDoc = options.annotate === false ? doc : annotated(doc, step, style);\n const data = await render(frameDoc, { timeUs: step.timeUs, width, height });\n const src = await options.store({ data, mimeType: options.mimeType ?? \"image/png\" }, step);\n steps.push({ ...step, image: { src, width, height, alt: `Step ${step.number}: ${step.title}` } });\n }\n return { ...guide, steps };\n}\n","/** A guide as Markdown, standalone HTML, or JSON. */\nimport type { Guide } from \"./build.js\";\n\nexport interface MarkdownOptions {\n /** Prefix for image paths (e.g. \"images/\"). */\n imageBase?: string;\n /** Heading level for the title (default 1). */\n level?: 1 | 2;\n}\n\nconst mdEscape = (s: string) => s.replace(/([\\\\`*_[\\]<>])/g, \"\\\\$1\");\n\nexport function toMarkdown(guide: Guide, options: MarkdownOptions = {}): string {\n const h = \"#\".repeat(options.level ?? 1);\n const lines = [`${h} ${mdEscape(guide.title)}`, \"\"];\n if (guide.intro) lines.push(mdEscape(guide.intro), \"\");\n for (const step of guide.steps) {\n lines.push(`${h}# ${step.number}. ${mdEscape(step.title)}`, \"\");\n if (step.body) lines.push(mdEscape(step.body), \"\");\n if (step.image) lines.push(`![${mdEscape(step.image.alt)}](${encodeURI((options.imageBase ?? \"\") + step.image.src)})`, \"\");\n }\n return `${lines.join(\"\\n\").trimEnd()}\\n`;\n}\n\nexport interface HtmlOptions {\n imageBase?: string;\n /** Accent colour (default \"#2563eb\"). */\n accent?: string;\n /** A full page (default) or just the article element. */\n fragment?: boolean;\n}\n\nconst esc = (s: string) => s.replace(/[&<>\"']/g, (c) => ({ \"&\": \"&amp;\", \"<\": \"&lt;\", \">\": \"&gt;\", '\"': \"&quot;\", \"'\": \"&#39;\" })[c]!);\n\nexport function toHtml(guide: Guide, options: HtmlOptions = {}): string {\n const accent = options.accent ?? \"#2563eb\";\n const steps = guide.steps\n .map(\n (s) => ` <li class=\"mc-step\" id=\"${esc(s.id)}\">\n <h2><span class=\"mc-n\">${s.number}</span>${esc(s.title)}</h2>${s.body ? `\\n <p>${esc(s.body)}</p>` : \"\"}${\n s.image ? `\\n <img src=\"${esc((options.imageBase ?? \"\") + s.image.src)}\" width=\"${s.image.width}\" height=\"${s.image.height}\" alt=\"${esc(s.image.alt)}\" loading=\"lazy\">` : \"\"\n }\n </li>`,\n )\n .join(\"\\n\");\n const article = `<article class=\"mc-guide\"${guide.language ? ` lang=\"${esc(guide.language)}\"` : \"\"}>\n <h1>${esc(guide.title)}</h1>${guide.intro ? `\\n <p class=\"mc-intro\">${esc(guide.intro)}</p>` : \"\"}\n <ol class=\"mc-steps\">\n${steps}\n </ol>\n</article>`;\n if (options.fragment) return article;\n return `<!doctype html>\n<html${guide.language ? ` lang=\"${esc(guide.language)}\"` : \"\"}>\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>${esc(guide.title)}</title>\n<style>\n :root { --accent: ${esc(accent)}; color-scheme: light dark; }\n body { margin: 0; font: 16px/1.6 system-ui, sans-serif; background: Canvas; color: CanvasText; }\n .mc-guide { max-width: 820px; margin: 0 auto; padding: 32px 16px 64px; }\n .mc-guide h1 { font-size: 2rem; line-height: 1.2; margin: 0 0 8px; }\n .mc-intro { opacity: .8; margin: 0 0 24px; }\n .mc-steps { list-style: none; padding: 0; margin: 0; }\n .mc-step { margin: 0 0 40px; }\n .mc-step h2 { display: flex; gap: 12px; align-items: center; font-size: 1.25rem; margin: 0 0 8px; }\n .mc-n { flex: none; display: inline-grid; place-items: center; width: 1.8em; height: 1.8em; border-radius: 50%; background: var(--accent); color: #fff; font-size: .9rem; }\n .mc-step p { margin: 0 0 12px; }\n .mc-step img { display: block; width: 100%; height: auto; border-radius: 10px; border: 1px solid color-mix(in srgb, CanvasText 15%, transparent); }\n</style>\n</head>\n<body>\n${article}\n</body>\n</html>\n`;\n}\n\nexport function toJson(guide: Guide): string {\n return `${JSON.stringify(guide, null, 2)}\\n`;\n}\n"],"mappings":"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","names":["import_core"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/build.ts","../src/stills.ts","../src/format.ts"],"sourcesContent":["export { buildGuide, titleForAction } from \"./build.js\";\r\nexport type { BuildGuideOptions, Guide, GuideImage, GuideStep } from \"./build.js\";\r\nexport { addStills, annotationTemplate } from \"./stills.js\";\r\nexport type { AddStillsOptions, AnnotationStyle, StillRenderer } from \"./stills.js\";\r\nexport { toHtml, toJson, toMarkdown } from \"./format.js\";\r\nexport type { HtmlOptions, MarkdownOptions } from \"./format.js\";\r\n","/**\r\n * A written guide from the same project as the video: one step per marker\r\n * (or per click), the step's text from the narration script or what was\r\n * said, and where the action happened on screen, for an annotated\r\n * screenshot. Pure: no rendering, no network.\r\n */\r\nimport { listMarkers, type ProjectDocument, type VideoClip } from \"@miraiclip/core\";\r\nimport type { CaptureEvent, CaptureSession, CaptureTarget } from \"@miraiclip/capture\";\r\nimport { framePointToCanvas, rectToFrame, screenClip, sessionToClipUs } from \"@miraiclip/screencast\";\r\nimport { detectFillers, transcriptOnTimeline, type Transcript } from \"@miraiclip/transcript\";\r\n\r\nexport interface GuideImage {\r\n src: string;\r\n width: number;\r\n height: number;\r\n alt: string;\r\n}\r\n\r\nexport interface GuideStep {\r\n id: string;\r\n /** 1-based. */\r\n number: number;\r\n title: string;\r\n /** Paragraph text (plain). */\r\n body?: string;\r\n /** Timeline time to screenshot. */\r\n timeUs: number;\r\n /** Where the action is, as fractions of the frame (canvas), at `timeUs`. */\r\n highlight?: { x: number; y: number; width: number; height: number };\r\n /** What the viewer does in this step, when the log knows. */\r\n action?: { kind: \"click\" | \"type\" | \"key\" | \"navigate\"; label?: string; key?: string; url?: string };\r\n markerId?: string;\r\n image?: GuideImage;\r\n}\r\n\r\nexport interface Guide {\r\n title: string;\r\n intro?: string;\r\n language?: string;\r\n steps: GuideStep[];\r\n}\r\n\r\nexport interface BuildGuideOptions {\r\n title: string;\r\n intro?: string;\r\n /** Steps from markers (default when the project has any) or from clicks. */\r\n stepsFrom?: \"markers\" | \"clicks\";\r\n /** Only these markers (default: all; markers with `data.kind === \"step\"` when any have it). */\r\n markerFilter?: (marker: { id: string; label: string; data?: Record<string, unknown> }) => boolean;\r\n /** The recording's interaction log and its clip: actions and highlights come from it. */\r\n session?: CaptureSession;\r\n clipId?: string;\r\n /**\r\n * Every clip of the recording, when it has been cut or split (by cleanup or\r\n * a voiceover's freeze frames): each action is found on the piece that\r\n * shows it. Overrides `clipId`.\r\n */\r\n clipIds?: string[];\r\n /** Step text from a narration script (by marker id or by time)… */\r\n script?: { language?: string; segments: { text: string; startUs: number; markerId?: string }[] };\r\n /** …or from what was said (fillers dropped). */\r\n transcript?: Transcript;\r\n /** Grow highlight boxes by this many CSS px of the recording (default 8). */\r\n highlightPaddingPx?: number;\r\n}\r\n\r\ntype Located = { local: number; clip: VideoClip; event: CaptureEvent; target?: CaptureTarget };\r\n\r\nfunction actionOf(e: CaptureEvent): GuideStep[\"action\"] | undefined {\r\n const label = \"target\" in e ? e.target?.label : undefined;\r\n switch (e.type) {\r\n case \"click\":\r\n case \"dblclick\":\r\n return { kind: \"click\", ...(label ? { label } : {}) };\r\n case \"input\":\r\n return { kind: \"type\", ...(label ? { label } : {}) };\r\n case \"key\":\r\n return e.key === \"text\" ? undefined : { kind: \"key\", key: e.key };\r\n case \"navigate\":\r\n return { kind: \"navigate\", url: e.url };\r\n default:\r\n return undefined;\r\n }\r\n}\r\n\r\n/** A step title from the action (\"Click “Save”\", \"Enter your email\", \"Press Enter\"). */\r\nexport function titleForAction(action: GuideStep[\"action\"]): string | undefined {\r\n if (!action) return undefined;\r\n if (action.kind === \"click\") return action.label ? `Click “${action.label}”` : undefined;\r\n if (action.kind === \"type\") return action.label ? (action.label.toLowerCase().startsWith(\"enter\") ? action.label : `Enter ${action.label.charAt(0).toLowerCase()}${action.label.slice(1)}`) : \"Type the value\";\r\n if (action.kind === \"key\") return `Press ${action.key!.replace(/\\+/g, \" + \")}`;\r\n return undefined;\r\n}\r\n\r\nexport function buildGuide(doc: ProjectDocument, options: BuildGuideOptions): Guide {\r\n const session = options.session;\r\n const clips = session ? (options.clipIds ?? (options.clipId ? [options.clipId] : [])).map((id) => screenClip(doc, id)) : [];\r\n const pad = options.highlightPaddingPx ?? 8;\r\n const located: Located[] = [];\r\n if (session) {\r\n for (const e of session.events) {\r\n if (![\"click\", \"dblclick\", \"input\", \"key\", \"navigate\"].includes(e.type)) continue;\r\n // The piece of the recording that shows this moment (none when it was cut away).\r\n for (const clip of clips) {\r\n const local = sessionToClipUs(clip, session, e.tUs);\r\n if (local === undefined) continue;\r\n located.push({ local, clip, event: e, ...(\"target\" in e && e.target ? { target: e.target } : {}) });\r\n break;\r\n }\r\n }\r\n located.sort((a, b) => a.clip.startUs + a.local - (b.clip.startUs + b.local));\r\n }\r\n const at = (l: Located) => l.clip.startUs + l.local;\r\n\r\n // The step spans.\r\n let markers = listMarkers(doc);\r\n if (markers.some((m) => m.data?.[\"kind\"] === \"step\")) markers = markers.filter((m) => m.data?.[\"kind\"] === \"step\");\r\n if (options.markerFilter) markers = markers.filter(options.markerFilter);\r\n const fromMarkers = (options.stepsFrom ?? (markers.length ? \"markers\" : \"clicks\")) === \"markers\";\r\n const end = clips.length ? Math.max(...clips.map((c) => c.startUs + c.durationUs)) : Math.max(0, ...Object.values(doc.clips).map((c) => c.startUs + c.durationUs));\r\n const spans: { startUs: number; endUs: number; markerId?: string; label?: string }[] = fromMarkers\r\n ? markers.map((m, i) => ({ startUs: m.timeUs, endUs: markers[i + 1]?.timeUs ?? end, markerId: m.id, label: m.label }))\r\n : located\r\n .filter((l) => l.event.type === \"click\" || l.event.type === \"dblclick\")\r\n .map((l, i, all) => ({ startUs: at(l), endUs: all[i + 1] ? at(all[i + 1]!) : end }));\r\n\r\n const words = options.transcript ? transcriptOnTimeline(doc, options.transcript) : [];\r\n const fillerIdx = new Set(options.transcript ? detectFillers(options.transcript).flatMap((s) => s.words) : []);\r\n const textFor = (span: (typeof spans)[number]) => {\r\n const fromScript = options.script?.segments.find((s) => (span.markerId && s.markerId === span.markerId) || (s.startUs >= span.startUs && s.startUs < span.endUs));\r\n if (fromScript) return fromScript.text;\r\n const said = words.filter((w) => w.timelineStartUs >= span.startUs && w.timelineStartUs < span.endUs && !fillerIdx.has(w.index)).map((w) => w.text);\r\n if (!said.length) return undefined;\r\n const text = said.join(\" \");\r\n return text.charAt(0).toUpperCase() + text.slice(1);\r\n };\r\n\r\n const steps: GuideStep[] = spans.map((span, i) => {\r\n // The step's main action: its first click, else its first typing, else a named key.\r\n const inSpan = located.filter((l) => at(l) >= span.startUs && at(l) < span.endUs);\r\n const main =\r\n inSpan.find((l) => l.event.type === \"click\" || l.event.type === \"dblclick\") ??\r\n inSpan.find((l) => l.event.type === \"input\") ??\r\n inSpan.find((l) => l.event.type === \"key\" && (l.event as { key: string }).key !== \"text\");\r\n const action = main ? actionOf(main.event) : undefined;\r\n // Screenshot just before the action (the control is visible, not yet changed); else mid-step.\r\n // (Kept on the action's own piece of the recording.)\r\n const timeUs = main ? Math.max(span.startUs, main.clip.startUs, at(main) - 150_000) : Math.round(span.startUs + Math.min(1_000_000, (span.endUs - span.startUs) / 2));\r\n let highlight: GuideStep[\"highlight\"];\r\n if (main?.target && session && !main.target.sensitive) {\r\n const box = rectToFrame(session, main.target.rect, pad);\r\n const local = timeUs - main.clip.startUs;\r\n const a = framePointToCanvas(doc, main.clip, local, box.x, box.y);\r\n const b = framePointToCanvas(doc, main.clip, local, box.x + box.width, box.y + box.height);\r\n highlight = { x: a.x, y: a.y, width: b.x - a.x, height: b.y - a.y };\r\n }\r\n const title = (fromMarkers && span.label) || titleForAction(action) || `Step ${i + 1}`;\r\n const body = textFor(span);\r\n return {\r\n id: span.markerId ? `step-${span.markerId}` : `step-${i + 1}`,\r\n number: i + 1,\r\n title,\r\n ...(body && body !== title ? { body } : {}),\r\n timeUs,\r\n ...(highlight ? { highlight } : {}),\r\n ...(action ? { action } : {}),\r\n ...(span.markerId ? { markerId: span.markerId } : {}),\r\n };\r\n });\r\n const language = options.script?.language ?? options.transcript?.language;\r\n return { title: options.title, ...(options.intro ? { intro: options.intro } : {}), ...(language ? { language } : {}), steps };\r\n}\r\n","/**\r\n * Screenshots for a guide: each step's frame rendered by whatever still\r\n * renderer you have (`createRenderSession().renderStill` on a server,\r\n * `renderProjectStill` in the browser), with the step's control outlined,\r\n * the rest dimmed and the step number beside it. The annotation is an html\r\n * clip added to a throwaway copy of the project, so the renderer needs\r\n * nothing new and your project is untouched.\r\n */\r\nimport { applyCommands, createProject, type ProjectDocument } from \"@miraiclip/core\";\r\nimport type { Guide, GuideStep } from \"./build.js\";\r\n\r\n/** Render one frame of a document to image bytes (PNG/JPEG/WebP). */\r\nexport type StillRenderer = (doc: ProjectDocument, options: { timeUs: number; width?: number; height?: number }) => Promise<Uint8Array | Blob>;\r\n\r\nexport interface AnnotationStyle {\r\n /** Outline colour (default \"#facc15\"). */\r\n color?: string;\r\n /** Darken outside the box, 0..1 (default 0.35; 0 turns it off). */\r\n dim?: number;\r\n /** Show the step number (default true). */\r\n number?: boolean;\r\n}\r\n\r\nexport interface AddStillsOptions {\r\n /** Save one image and return its `src` (path or URL) as the guide will reference it. */\r\n store: (image: { data: Uint8Array | Blob; mimeType: string }, step: GuideStep) => Promise<string>;\r\n /** Output size (default: the project's). */\r\n width?: number;\r\n height?: number;\r\n /** Outline the step's control (default true). */\r\n annotate?: boolean | AnnotationStyle;\r\n /** MIME type the renderer produces (default \"image/png\"). */\r\n mimeType?: string;\r\n}\r\n\r\n/** The overlay markup (full-frame html clip) for one step. */\r\nexport function annotationTemplate(): string {\r\n return `<div style=\"position:relative;width:100%;height:100%;overflow:hidden;font-family:Inter,system-ui,sans-serif\">\r\n<div style=\"position:absolute;left:{{x}}%;top:{{y}}%;width:{{w}}%;height:{{h}}%;box-sizing:border-box;border:{{stroke}}px solid {{color}};border-radius:{{radius}}px;box-shadow:0 0 0 6000px rgba(0,0,0,{{dim}})\"></div>\r\n<div style=\"position:absolute;left:calc({{x}}% - {{badge}}px * .5);top:calc({{y}}% - {{badge}}px * .5);width:{{badge}}px;height:{{badge}}px;border-radius:50%;background:{{color}};color:#111;display:{{showNumber}};align-items:center;justify-content:center;font-weight:700;font-size:calc({{badge}}px * .55);box-shadow:0 2px 8px rgba(0,0,0,.4)\">{{n}}</div>\r\n</div>`;\r\n}\r\n\r\nfunction annotated(doc: ProjectDocument, step: GuideStep, style: AnnotationStyle): ProjectDocument {\r\n if (!step.highlight) return doc;\r\n const p = createProject(structuredClone(doc), { historyLimit: 1 });\r\n const h = step.highlight;\r\n const unit = Math.min(doc.settings.width, doc.settings.height) / 1080;\r\n const fps = doc.settings.fps || 30;\r\n const pct = (v: number) => Math.round(v * 100_000) / 1000;\r\n const r = applyCommands(p, [\r\n { type: \"track/add\", payload: { id: \"guide-annotation\", kind: \"video\" } },\r\n {\r\n type: \"clip/add\",\r\n payload: {\r\n kind: \"html\",\r\n id: \"guide-annotation\",\r\n trackId: \"guide-annotation\",\r\n startUs: Math.max(0, step.timeUs - Math.round(1e6 / fps)),\r\n durationUs: Math.round((2 * 1e6) / fps),\r\n template: annotationTemplate(),\r\n params: {\r\n x: pct(h.x),\r\n y: pct(h.y),\r\n w: pct(h.width),\r\n h: pct(h.height),\r\n stroke: Math.max(2, Math.round(5 * unit)),\r\n radius: Math.round(12 * unit),\r\n badge: Math.round(44 * unit),\r\n color: style.color ?? \"#facc15\",\r\n dim: style.dim ?? 0.35,\r\n showNumber: style.number === false ? \"none\" : \"flex\",\r\n n: step.number,\r\n },\r\n },\r\n },\r\n ]);\r\n if (!r.ok) throw new Error(`guide annotation failed: ${JSON.stringify(r.error)}`);\r\n return p.getState().doc;\r\n}\r\n\r\n/** Render, annotate and store a screenshot for every step; returns the guide with `image` set. */\r\nexport async function addStills(guide: Guide, doc: ProjectDocument, render: StillRenderer, options: AddStillsOptions): Promise<Guide> {\r\n const style: AnnotationStyle = typeof options.annotate === \"object\" ? options.annotate : {};\r\n const width = options.width ?? doc.settings.width;\r\n const height = options.height ?? Math.round((width * doc.settings.height) / doc.settings.width);\r\n const steps: GuideStep[] = [];\r\n for (const step of guide.steps) {\r\n const frameDoc = options.annotate === false ? doc : annotated(doc, step, style);\r\n const data = await render(frameDoc, { timeUs: step.timeUs, width, height });\r\n const src = await options.store({ data, mimeType: options.mimeType ?? \"image/png\" }, step);\r\n steps.push({ ...step, image: { src, width, height, alt: `Step ${step.number}: ${step.title}` } });\r\n }\r\n return { ...guide, steps };\r\n}\r\n","/** A guide as Markdown, standalone HTML, or JSON. */\r\nimport type { Guide } from \"./build.js\";\r\n\r\nexport interface MarkdownOptions {\r\n /** Prefix for image paths (e.g. \"images/\"). */\r\n imageBase?: string;\r\n /** Heading level for the title (default 1). */\r\n level?: 1 | 2;\r\n}\r\n\r\nconst mdEscape = (s: string) => s.replace(/([\\\\`*_[\\]<>])/g, \"\\\\$1\");\r\n\r\nexport function toMarkdown(guide: Guide, options: MarkdownOptions = {}): string {\r\n const h = \"#\".repeat(options.level ?? 1);\r\n const lines = [`${h} ${mdEscape(guide.title)}`, \"\"];\r\n if (guide.intro) lines.push(mdEscape(guide.intro), \"\");\r\n for (const step of guide.steps) {\r\n lines.push(`${h}# ${step.number}. ${mdEscape(step.title)}`, \"\");\r\n if (step.body) lines.push(mdEscape(step.body), \"\");\r\n if (step.image) lines.push(`![${mdEscape(step.image.alt)}](${encodeURI((options.imageBase ?? \"\") + step.image.src)})`, \"\");\r\n }\r\n return `${lines.join(\"\\n\").trimEnd()}\\n`;\r\n}\r\n\r\nexport interface HtmlOptions {\r\n imageBase?: string;\r\n /** Accent colour (default \"#2563eb\"). */\r\n accent?: string;\r\n /** A full page (default) or just the article element. */\r\n fragment?: boolean;\r\n}\r\n\r\nconst esc = (s: string) => s.replace(/[&<>\"']/g, (c) => ({ \"&\": \"&amp;\", \"<\": \"&lt;\", \">\": \"&gt;\", '\"': \"&quot;\", \"'\": \"&#39;\" })[c]!);\r\n\r\nexport function toHtml(guide: Guide, options: HtmlOptions = {}): string {\r\n const accent = options.accent ?? \"#2563eb\";\r\n const steps = guide.steps\r\n .map(\r\n (s) => ` <li class=\"mc-step\" id=\"${esc(s.id)}\">\r\n <h2><span class=\"mc-n\">${s.number}</span>${esc(s.title)}</h2>${s.body ? `\\n <p>${esc(s.body)}</p>` : \"\"}${\r\n s.image ? `\\n <img src=\"${esc((options.imageBase ?? \"\") + s.image.src)}\" width=\"${s.image.width}\" height=\"${s.image.height}\" alt=\"${esc(s.image.alt)}\" loading=\"lazy\">` : \"\"\r\n }\r\n </li>`,\r\n )\r\n .join(\"\\n\");\r\n const article = `<article class=\"mc-guide\"${guide.language ? ` lang=\"${esc(guide.language)}\"` : \"\"}>\r\n <h1>${esc(guide.title)}</h1>${guide.intro ? `\\n <p class=\"mc-intro\">${esc(guide.intro)}</p>` : \"\"}\r\n <ol class=\"mc-steps\">\r\n${steps}\r\n </ol>\r\n</article>`;\r\n if (options.fragment) return article;\r\n return `<!doctype html>\r\n<html${guide.language ? ` lang=\"${esc(guide.language)}\"` : \"\"}>\r\n<head>\r\n<meta charset=\"utf-8\">\r\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\r\n<title>${esc(guide.title)}</title>\r\n<style>\r\n :root { --accent: ${esc(accent)}; color-scheme: light dark; }\r\n body { margin: 0; font: 16px/1.6 system-ui, sans-serif; background: Canvas; color: CanvasText; }\r\n .mc-guide { max-width: 820px; margin: 0 auto; padding: 32px 16px 64px; }\r\n .mc-guide h1 { font-size: 2rem; line-height: 1.2; margin: 0 0 8px; }\r\n .mc-intro { opacity: .8; margin: 0 0 24px; }\r\n .mc-steps { list-style: none; padding: 0; margin: 0; }\r\n .mc-step { margin: 0 0 40px; }\r\n .mc-step h2 { display: flex; gap: 12px; align-items: center; font-size: 1.25rem; margin: 0 0 8px; }\r\n .mc-n { flex: none; display: inline-grid; place-items: center; width: 1.8em; height: 1.8em; border-radius: 50%; background: var(--accent); color: #fff; font-size: .9rem; }\r\n .mc-step p { margin: 0 0 12px; }\r\n .mc-step img { display: block; width: 100%; height: auto; border-radius: 10px; border: 1px solid color-mix(in srgb, CanvasText 15%, transparent); }\r\n</style>\r\n</head>\r\n<body>\r\n${article}\r\n</body>\r\n</html>\r\n`;\r\n}\r\n\r\nexport function toJson(guide: Guide): string {\r\n return `${JSON.stringify(guide, null, 2)}\\n`;\r\n}\r\n"],"mappings":"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","names":["import_core"]}
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/build.ts","../src/stills.ts","../src/format.ts"],"sourcesContent":["/**\n * A written guide from the same project as the video: one step per marker\n * (or per click), the step's text from the narration script or what was\n * said, and where the action happened on screen, for an annotated\n * screenshot. Pure: no rendering, no network.\n */\nimport { listMarkers, type ProjectDocument, type VideoClip } from \"@miraiclip/core\";\nimport type { CaptureEvent, CaptureSession, CaptureTarget } from \"@miraiclip/capture\";\nimport { framePointToCanvas, rectToFrame, screenClip, sessionToClipUs } from \"@miraiclip/screencast\";\nimport { detectFillers, transcriptOnTimeline, type Transcript } from \"@miraiclip/transcript\";\n\nexport interface GuideImage {\n src: string;\n width: number;\n height: number;\n alt: string;\n}\n\nexport interface GuideStep {\n id: string;\n /** 1-based. */\n number: number;\n title: string;\n /** Paragraph text (plain). */\n body?: string;\n /** Timeline time to screenshot. */\n timeUs: number;\n /** Where the action is, as fractions of the frame (canvas), at `timeUs`. */\n highlight?: { x: number; y: number; width: number; height: number };\n /** What the viewer does in this step, when the log knows. */\n action?: { kind: \"click\" | \"type\" | \"key\" | \"navigate\"; label?: string; key?: string; url?: string };\n markerId?: string;\n image?: GuideImage;\n}\n\nexport interface Guide {\n title: string;\n intro?: string;\n language?: string;\n steps: GuideStep[];\n}\n\nexport interface BuildGuideOptions {\n title: string;\n intro?: string;\n /** Steps from markers (default when the project has any) or from clicks. */\n stepsFrom?: \"markers\" | \"clicks\";\n /** Only these markers (default: all; markers with `data.kind === \"step\"` when any have it). */\n markerFilter?: (marker: { id: string; label: string; data?: Record<string, unknown> }) => boolean;\n /** The recording's interaction log and its clip: actions and highlights come from it. */\n session?: CaptureSession;\n clipId?: string;\n /**\n * Every clip of the recording, when it has been cut or split (by cleanup or\n * a voiceover's freeze frames): each action is found on the piece that\n * shows it. Overrides `clipId`.\n */\n clipIds?: string[];\n /** Step text from a narration script (by marker id or by time)… */\n script?: { language?: string; segments: { text: string; startUs: number; markerId?: string }[] };\n /** …or from what was said (fillers dropped). */\n transcript?: Transcript;\n /** Grow highlight boxes by this many CSS px of the recording (default 8). */\n highlightPaddingPx?: number;\n}\n\ntype Located = { local: number; clip: VideoClip; event: CaptureEvent; target?: CaptureTarget };\n\nfunction actionOf(e: CaptureEvent): GuideStep[\"action\"] | undefined {\n const label = \"target\" in e ? e.target?.label : undefined;\n switch (e.type) {\n case \"click\":\n case \"dblclick\":\n return { kind: \"click\", ...(label ? { label } : {}) };\n case \"input\":\n return { kind: \"type\", ...(label ? { label } : {}) };\n case \"key\":\n return e.key === \"text\" ? undefined : { kind: \"key\", key: e.key };\n case \"navigate\":\n return { kind: \"navigate\", url: e.url };\n default:\n return undefined;\n }\n}\n\n/** A step title from the action (\"Click “Save”\", \"Enter your email\", \"Press Enter\"). */\nexport function titleForAction(action: GuideStep[\"action\"]): string | undefined {\n if (!action) return undefined;\n if (action.kind === \"click\") return action.label ? `Click “${action.label}”` : undefined;\n if (action.kind === \"type\") return action.label ? (action.label.toLowerCase().startsWith(\"enter\") ? action.label : `Enter ${action.label.charAt(0).toLowerCase()}${action.label.slice(1)}`) : \"Type the value\";\n if (action.kind === \"key\") return `Press ${action.key!.replace(/\\+/g, \" + \")}`;\n return undefined;\n}\n\nexport function buildGuide(doc: ProjectDocument, options: BuildGuideOptions): Guide {\n const session = options.session;\n const clips = session ? (options.clipIds ?? (options.clipId ? [options.clipId] : [])).map((id) => screenClip(doc, id)) : [];\n const pad = options.highlightPaddingPx ?? 8;\n const located: Located[] = [];\n if (session) {\n for (const e of session.events) {\n if (![\"click\", \"dblclick\", \"input\", \"key\", \"navigate\"].includes(e.type)) continue;\n // The piece of the recording that shows this moment (none when it was cut away).\n for (const clip of clips) {\n const local = sessionToClipUs(clip, session, e.tUs);\n if (local === undefined) continue;\n located.push({ local, clip, event: e, ...(\"target\" in e && e.target ? { target: e.target } : {}) });\n break;\n }\n }\n located.sort((a, b) => a.clip.startUs + a.local - (b.clip.startUs + b.local));\n }\n const at = (l: Located) => l.clip.startUs + l.local;\n\n // The step spans.\n let markers = listMarkers(doc);\n if (markers.some((m) => m.data?.[\"kind\"] === \"step\")) markers = markers.filter((m) => m.data?.[\"kind\"] === \"step\");\n if (options.markerFilter) markers = markers.filter(options.markerFilter);\n const fromMarkers = (options.stepsFrom ?? (markers.length ? \"markers\" : \"clicks\")) === \"markers\";\n const end = clips.length ? Math.max(...clips.map((c) => c.startUs + c.durationUs)) : Math.max(0, ...Object.values(doc.clips).map((c) => c.startUs + c.durationUs));\n const spans: { startUs: number; endUs: number; markerId?: string; label?: string }[] = fromMarkers\n ? markers.map((m, i) => ({ startUs: m.timeUs, endUs: markers[i + 1]?.timeUs ?? end, markerId: m.id, label: m.label }))\n : located\n .filter((l) => l.event.type === \"click\" || l.event.type === \"dblclick\")\n .map((l, i, all) => ({ startUs: at(l), endUs: all[i + 1] ? at(all[i + 1]!) : end }));\n\n const words = options.transcript ? transcriptOnTimeline(doc, options.transcript) : [];\n const fillerIdx = new Set(options.transcript ? detectFillers(options.transcript).flatMap((s) => s.words) : []);\n const textFor = (span: (typeof spans)[number]) => {\n const fromScript = options.script?.segments.find((s) => (span.markerId && s.markerId === span.markerId) || (s.startUs >= span.startUs && s.startUs < span.endUs));\n if (fromScript) return fromScript.text;\n const said = words.filter((w) => w.timelineStartUs >= span.startUs && w.timelineStartUs < span.endUs && !fillerIdx.has(w.index)).map((w) => w.text);\n if (!said.length) return undefined;\n const text = said.join(\" \");\n return text.charAt(0).toUpperCase() + text.slice(1);\n };\n\n const steps: GuideStep[] = spans.map((span, i) => {\n // The step's main action: its first click, else its first typing, else a named key.\n const inSpan = located.filter((l) => at(l) >= span.startUs && at(l) < span.endUs);\n const main =\n inSpan.find((l) => l.event.type === \"click\" || l.event.type === \"dblclick\") ??\n inSpan.find((l) => l.event.type === \"input\") ??\n inSpan.find((l) => l.event.type === \"key\" && (l.event as { key: string }).key !== \"text\");\n const action = main ? actionOf(main.event) : undefined;\n // Screenshot just before the action (the control is visible, not yet changed); else mid-step.\n // (Kept on the action's own piece of the recording.)\n const timeUs = main ? Math.max(span.startUs, main.clip.startUs, at(main) - 150_000) : Math.round(span.startUs + Math.min(1_000_000, (span.endUs - span.startUs) / 2));\n let highlight: GuideStep[\"highlight\"];\n if (main?.target && session && !main.target.sensitive) {\n const box = rectToFrame(session, main.target.rect, pad);\n const local = timeUs - main.clip.startUs;\n const a = framePointToCanvas(doc, main.clip, local, box.x, box.y);\n const b = framePointToCanvas(doc, main.clip, local, box.x + box.width, box.y + box.height);\n highlight = { x: a.x, y: a.y, width: b.x - a.x, height: b.y - a.y };\n }\n const title = (fromMarkers && span.label) || titleForAction(action) || `Step ${i + 1}`;\n const body = textFor(span);\n return {\n id: span.markerId ? `step-${span.markerId}` : `step-${i + 1}`,\n number: i + 1,\n title,\n ...(body && body !== title ? { body } : {}),\n timeUs,\n ...(highlight ? { highlight } : {}),\n ...(action ? { action } : {}),\n ...(span.markerId ? { markerId: span.markerId } : {}),\n };\n });\n const language = options.script?.language ?? options.transcript?.language;\n return { title: options.title, ...(options.intro ? { intro: options.intro } : {}), ...(language ? { language } : {}), steps };\n}\n","/**\n * Screenshots for a guide: each step's frame rendered by whatever still\n * renderer you have (`createRenderSession().renderStill` on a server,\n * `renderProjectStill` in the browser), with the step's control outlined,\n * the rest dimmed and the step number beside it. The annotation is an html\n * clip added to a throwaway copy of the project, so the renderer needs\n * nothing new and your project is untouched.\n */\nimport { applyCommands, createProject, type ProjectDocument } from \"@miraiclip/core\";\nimport type { Guide, GuideStep } from \"./build.js\";\n\n/** Render one frame of a document to image bytes (PNG/JPEG/WebP). */\nexport type StillRenderer = (doc: ProjectDocument, options: { timeUs: number; width?: number; height?: number }) => Promise<Uint8Array | Blob>;\n\nexport interface AnnotationStyle {\n /** Outline colour (default \"#facc15\"). */\n color?: string;\n /** Darken outside the box, 0..1 (default 0.35; 0 turns it off). */\n dim?: number;\n /** Show the step number (default true). */\n number?: boolean;\n}\n\nexport interface AddStillsOptions {\n /** Save one image and return its `src` (path or URL) as the guide will reference it. */\n store: (image: { data: Uint8Array | Blob; mimeType: string }, step: GuideStep) => Promise<string>;\n /** Output size (default: the project's). */\n width?: number;\n height?: number;\n /** Outline the step's control (default true). */\n annotate?: boolean | AnnotationStyle;\n /** MIME type the renderer produces (default \"image/png\"). */\n mimeType?: string;\n}\n\n/** The overlay markup (full-frame html clip) for one step. */\nexport function annotationTemplate(): string {\n return `<div style=\"position:relative;width:100%;height:100%;overflow:hidden;font-family:Inter,system-ui,sans-serif\">\n<div style=\"position:absolute;left:{{x}}%;top:{{y}}%;width:{{w}}%;height:{{h}}%;box-sizing:border-box;border:{{stroke}}px solid {{color}};border-radius:{{radius}}px;box-shadow:0 0 0 6000px rgba(0,0,0,{{dim}})\"></div>\n<div style=\"position:absolute;left:calc({{x}}% - {{badge}}px * .5);top:calc({{y}}% - {{badge}}px * .5);width:{{badge}}px;height:{{badge}}px;border-radius:50%;background:{{color}};color:#111;display:{{showNumber}};align-items:center;justify-content:center;font-weight:700;font-size:calc({{badge}}px * .55);box-shadow:0 2px 8px rgba(0,0,0,.4)\">{{n}}</div>\n</div>`;\n}\n\nfunction annotated(doc: ProjectDocument, step: GuideStep, style: AnnotationStyle): ProjectDocument {\n if (!step.highlight) return doc;\n const p = createProject(structuredClone(doc), { historyLimit: 1 });\n const h = step.highlight;\n const unit = Math.min(doc.settings.width, doc.settings.height) / 1080;\n const fps = doc.settings.fps || 30;\n const pct = (v: number) => Math.round(v * 100_000) / 1000;\n const r = applyCommands(p, [\n { type: \"track/add\", payload: { id: \"guide-annotation\", kind: \"video\" } },\n {\n type: \"clip/add\",\n payload: {\n kind: \"html\",\n id: \"guide-annotation\",\n trackId: \"guide-annotation\",\n startUs: Math.max(0, step.timeUs - Math.round(1e6 / fps)),\n durationUs: Math.round((2 * 1e6) / fps),\n template: annotationTemplate(),\n params: {\n x: pct(h.x),\n y: pct(h.y),\n w: pct(h.width),\n h: pct(h.height),\n stroke: Math.max(2, Math.round(5 * unit)),\n radius: Math.round(12 * unit),\n badge: Math.round(44 * unit),\n color: style.color ?? \"#facc15\",\n dim: style.dim ?? 0.35,\n showNumber: style.number === false ? \"none\" : \"flex\",\n n: step.number,\n },\n },\n },\n ]);\n if (!r.ok) throw new Error(`guide annotation failed: ${JSON.stringify(r.error)}`);\n return p.getState().doc;\n}\n\n/** Render, annotate and store a screenshot for every step; returns the guide with `image` set. */\nexport async function addStills(guide: Guide, doc: ProjectDocument, render: StillRenderer, options: AddStillsOptions): Promise<Guide> {\n const style: AnnotationStyle = typeof options.annotate === \"object\" ? options.annotate : {};\n const width = options.width ?? doc.settings.width;\n const height = options.height ?? Math.round((width * doc.settings.height) / doc.settings.width);\n const steps: GuideStep[] = [];\n for (const step of guide.steps) {\n const frameDoc = options.annotate === false ? doc : annotated(doc, step, style);\n const data = await render(frameDoc, { timeUs: step.timeUs, width, height });\n const src = await options.store({ data, mimeType: options.mimeType ?? \"image/png\" }, step);\n steps.push({ ...step, image: { src, width, height, alt: `Step ${step.number}: ${step.title}` } });\n }\n return { ...guide, steps };\n}\n","/** A guide as Markdown, standalone HTML, or JSON. */\nimport type { Guide } from \"./build.js\";\n\nexport interface MarkdownOptions {\n /** Prefix for image paths (e.g. \"images/\"). */\n imageBase?: string;\n /** Heading level for the title (default 1). */\n level?: 1 | 2;\n}\n\nconst mdEscape = (s: string) => s.replace(/([\\\\`*_[\\]<>])/g, \"\\\\$1\");\n\nexport function toMarkdown(guide: Guide, options: MarkdownOptions = {}): string {\n const h = \"#\".repeat(options.level ?? 1);\n const lines = [`${h} ${mdEscape(guide.title)}`, \"\"];\n if (guide.intro) lines.push(mdEscape(guide.intro), \"\");\n for (const step of guide.steps) {\n lines.push(`${h}# ${step.number}. ${mdEscape(step.title)}`, \"\");\n if (step.body) lines.push(mdEscape(step.body), \"\");\n if (step.image) lines.push(`![${mdEscape(step.image.alt)}](${encodeURI((options.imageBase ?? \"\") + step.image.src)})`, \"\");\n }\n return `${lines.join(\"\\n\").trimEnd()}\\n`;\n}\n\nexport interface HtmlOptions {\n imageBase?: string;\n /** Accent colour (default \"#2563eb\"). */\n accent?: string;\n /** A full page (default) or just the article element. */\n fragment?: boolean;\n}\n\nconst esc = (s: string) => s.replace(/[&<>\"']/g, (c) => ({ \"&\": \"&amp;\", \"<\": \"&lt;\", \">\": \"&gt;\", '\"': \"&quot;\", \"'\": \"&#39;\" })[c]!);\n\nexport function toHtml(guide: Guide, options: HtmlOptions = {}): string {\n const accent = options.accent ?? \"#2563eb\";\n const steps = guide.steps\n .map(\n (s) => ` <li class=\"mc-step\" id=\"${esc(s.id)}\">\n <h2><span class=\"mc-n\">${s.number}</span>${esc(s.title)}</h2>${s.body ? `\\n <p>${esc(s.body)}</p>` : \"\"}${\n s.image ? `\\n <img src=\"${esc((options.imageBase ?? \"\") + s.image.src)}\" width=\"${s.image.width}\" height=\"${s.image.height}\" alt=\"${esc(s.image.alt)}\" loading=\"lazy\">` : \"\"\n }\n </li>`,\n )\n .join(\"\\n\");\n const article = `<article class=\"mc-guide\"${guide.language ? ` lang=\"${esc(guide.language)}\"` : \"\"}>\n <h1>${esc(guide.title)}</h1>${guide.intro ? `\\n <p class=\"mc-intro\">${esc(guide.intro)}</p>` : \"\"}\n <ol class=\"mc-steps\">\n${steps}\n </ol>\n</article>`;\n if (options.fragment) return article;\n return `<!doctype html>\n<html${guide.language ? ` lang=\"${esc(guide.language)}\"` : \"\"}>\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>${esc(guide.title)}</title>\n<style>\n :root { --accent: ${esc(accent)}; color-scheme: light dark; }\n body { margin: 0; font: 16px/1.6 system-ui, sans-serif; background: Canvas; color: CanvasText; }\n .mc-guide { max-width: 820px; margin: 0 auto; padding: 32px 16px 64px; }\n .mc-guide h1 { font-size: 2rem; line-height: 1.2; margin: 0 0 8px; }\n .mc-intro { opacity: .8; margin: 0 0 24px; }\n .mc-steps { list-style: none; padding: 0; margin: 0; }\n .mc-step { margin: 0 0 40px; }\n .mc-step h2 { display: flex; gap: 12px; align-items: center; font-size: 1.25rem; margin: 0 0 8px; }\n .mc-n { flex: none; display: inline-grid; place-items: center; width: 1.8em; height: 1.8em; border-radius: 50%; background: var(--accent); color: #fff; font-size: .9rem; }\n .mc-step p { margin: 0 0 12px; }\n .mc-step img { display: block; width: 100%; height: auto; border-radius: 10px; border: 1px solid color-mix(in srgb, CanvasText 15%, transparent); }\n</style>\n</head>\n<body>\n${article}\n</body>\n</html>\n`;\n}\n\nexport function toJson(guide: Guide): string {\n return `${JSON.stringify(guide, null, 2)}\\n`;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/build.ts","../src/stills.ts","../src/format.ts"],"sourcesContent":["/**\r\n * A written guide from the same project as the video: one step per marker\r\n * (or per click), the step's text from the narration script or what was\r\n * said, and where the action happened on screen, for an annotated\r\n * screenshot. Pure: no rendering, no network.\r\n */\r\nimport { listMarkers, type ProjectDocument, type VideoClip } from \"@miraiclip/core\";\r\nimport type { CaptureEvent, CaptureSession, CaptureTarget } from \"@miraiclip/capture\";\r\nimport { framePointToCanvas, rectToFrame, screenClip, sessionToClipUs } from \"@miraiclip/screencast\";\r\nimport { detectFillers, transcriptOnTimeline, type Transcript } from \"@miraiclip/transcript\";\r\n\r\nexport interface GuideImage {\r\n src: string;\r\n width: number;\r\n height: number;\r\n alt: string;\r\n}\r\n\r\nexport interface GuideStep {\r\n id: string;\r\n /** 1-based. */\r\n number: number;\r\n title: string;\r\n /** Paragraph text (plain). */\r\n body?: string;\r\n /** Timeline time to screenshot. */\r\n timeUs: number;\r\n /** Where the action is, as fractions of the frame (canvas), at `timeUs`. */\r\n highlight?: { x: number; y: number; width: number; height: number };\r\n /** What the viewer does in this step, when the log knows. */\r\n action?: { kind: \"click\" | \"type\" | \"key\" | \"navigate\"; label?: string; key?: string; url?: string };\r\n markerId?: string;\r\n image?: GuideImage;\r\n}\r\n\r\nexport interface Guide {\r\n title: string;\r\n intro?: string;\r\n language?: string;\r\n steps: GuideStep[];\r\n}\r\n\r\nexport interface BuildGuideOptions {\r\n title: string;\r\n intro?: string;\r\n /** Steps from markers (default when the project has any) or from clicks. */\r\n stepsFrom?: \"markers\" | \"clicks\";\r\n /** Only these markers (default: all; markers with `data.kind === \"step\"` when any have it). */\r\n markerFilter?: (marker: { id: string; label: string; data?: Record<string, unknown> }) => boolean;\r\n /** The recording's interaction log and its clip: actions and highlights come from it. */\r\n session?: CaptureSession;\r\n clipId?: string;\r\n /**\r\n * Every clip of the recording, when it has been cut or split (by cleanup or\r\n * a voiceover's freeze frames): each action is found on the piece that\r\n * shows it. Overrides `clipId`.\r\n */\r\n clipIds?: string[];\r\n /** Step text from a narration script (by marker id or by time)… */\r\n script?: { language?: string; segments: { text: string; startUs: number; markerId?: string }[] };\r\n /** …or from what was said (fillers dropped). */\r\n transcript?: Transcript;\r\n /** Grow highlight boxes by this many CSS px of the recording (default 8). */\r\n highlightPaddingPx?: number;\r\n}\r\n\r\ntype Located = { local: number; clip: VideoClip; event: CaptureEvent; target?: CaptureTarget };\r\n\r\nfunction actionOf(e: CaptureEvent): GuideStep[\"action\"] | undefined {\r\n const label = \"target\" in e ? e.target?.label : undefined;\r\n switch (e.type) {\r\n case \"click\":\r\n case \"dblclick\":\r\n return { kind: \"click\", ...(label ? { label } : {}) };\r\n case \"input\":\r\n return { kind: \"type\", ...(label ? { label } : {}) };\r\n case \"key\":\r\n return e.key === \"text\" ? undefined : { kind: \"key\", key: e.key };\r\n case \"navigate\":\r\n return { kind: \"navigate\", url: e.url };\r\n default:\r\n return undefined;\r\n }\r\n}\r\n\r\n/** A step title from the action (\"Click “Save”\", \"Enter your email\", \"Press Enter\"). */\r\nexport function titleForAction(action: GuideStep[\"action\"]): string | undefined {\r\n if (!action) return undefined;\r\n if (action.kind === \"click\") return action.label ? `Click “${action.label}”` : undefined;\r\n if (action.kind === \"type\") return action.label ? (action.label.toLowerCase().startsWith(\"enter\") ? action.label : `Enter ${action.label.charAt(0).toLowerCase()}${action.label.slice(1)}`) : \"Type the value\";\r\n if (action.kind === \"key\") return `Press ${action.key!.replace(/\\+/g, \" + \")}`;\r\n return undefined;\r\n}\r\n\r\nexport function buildGuide(doc: ProjectDocument, options: BuildGuideOptions): Guide {\r\n const session = options.session;\r\n const clips = session ? (options.clipIds ?? (options.clipId ? [options.clipId] : [])).map((id) => screenClip(doc, id)) : [];\r\n const pad = options.highlightPaddingPx ?? 8;\r\n const located: Located[] = [];\r\n if (session) {\r\n for (const e of session.events) {\r\n if (![\"click\", \"dblclick\", \"input\", \"key\", \"navigate\"].includes(e.type)) continue;\r\n // The piece of the recording that shows this moment (none when it was cut away).\r\n for (const clip of clips) {\r\n const local = sessionToClipUs(clip, session, e.tUs);\r\n if (local === undefined) continue;\r\n located.push({ local, clip, event: e, ...(\"target\" in e && e.target ? { target: e.target } : {}) });\r\n break;\r\n }\r\n }\r\n located.sort((a, b) => a.clip.startUs + a.local - (b.clip.startUs + b.local));\r\n }\r\n const at = (l: Located) => l.clip.startUs + l.local;\r\n\r\n // The step spans.\r\n let markers = listMarkers(doc);\r\n if (markers.some((m) => m.data?.[\"kind\"] === \"step\")) markers = markers.filter((m) => m.data?.[\"kind\"] === \"step\");\r\n if (options.markerFilter) markers = markers.filter(options.markerFilter);\r\n const fromMarkers = (options.stepsFrom ?? (markers.length ? \"markers\" : \"clicks\")) === \"markers\";\r\n const end = clips.length ? Math.max(...clips.map((c) => c.startUs + c.durationUs)) : Math.max(0, ...Object.values(doc.clips).map((c) => c.startUs + c.durationUs));\r\n const spans: { startUs: number; endUs: number; markerId?: string; label?: string }[] = fromMarkers\r\n ? markers.map((m, i) => ({ startUs: m.timeUs, endUs: markers[i + 1]?.timeUs ?? end, markerId: m.id, label: m.label }))\r\n : located\r\n .filter((l) => l.event.type === \"click\" || l.event.type === \"dblclick\")\r\n .map((l, i, all) => ({ startUs: at(l), endUs: all[i + 1] ? at(all[i + 1]!) : end }));\r\n\r\n const words = options.transcript ? transcriptOnTimeline(doc, options.transcript) : [];\r\n const fillerIdx = new Set(options.transcript ? detectFillers(options.transcript).flatMap((s) => s.words) : []);\r\n const textFor = (span: (typeof spans)[number]) => {\r\n const fromScript = options.script?.segments.find((s) => (span.markerId && s.markerId === span.markerId) || (s.startUs >= span.startUs && s.startUs < span.endUs));\r\n if (fromScript) return fromScript.text;\r\n const said = words.filter((w) => w.timelineStartUs >= span.startUs && w.timelineStartUs < span.endUs && !fillerIdx.has(w.index)).map((w) => w.text);\r\n if (!said.length) return undefined;\r\n const text = said.join(\" \");\r\n return text.charAt(0).toUpperCase() + text.slice(1);\r\n };\r\n\r\n const steps: GuideStep[] = spans.map((span, i) => {\r\n // The step's main action: its first click, else its first typing, else a named key.\r\n const inSpan = located.filter((l) => at(l) >= span.startUs && at(l) < span.endUs);\r\n const main =\r\n inSpan.find((l) => l.event.type === \"click\" || l.event.type === \"dblclick\") ??\r\n inSpan.find((l) => l.event.type === \"input\") ??\r\n inSpan.find((l) => l.event.type === \"key\" && (l.event as { key: string }).key !== \"text\");\r\n const action = main ? actionOf(main.event) : undefined;\r\n // Screenshot just before the action (the control is visible, not yet changed); else mid-step.\r\n // (Kept on the action's own piece of the recording.)\r\n const timeUs = main ? Math.max(span.startUs, main.clip.startUs, at(main) - 150_000) : Math.round(span.startUs + Math.min(1_000_000, (span.endUs - span.startUs) / 2));\r\n let highlight: GuideStep[\"highlight\"];\r\n if (main?.target && session && !main.target.sensitive) {\r\n const box = rectToFrame(session, main.target.rect, pad);\r\n const local = timeUs - main.clip.startUs;\r\n const a = framePointToCanvas(doc, main.clip, local, box.x, box.y);\r\n const b = framePointToCanvas(doc, main.clip, local, box.x + box.width, box.y + box.height);\r\n highlight = { x: a.x, y: a.y, width: b.x - a.x, height: b.y - a.y };\r\n }\r\n const title = (fromMarkers && span.label) || titleForAction(action) || `Step ${i + 1}`;\r\n const body = textFor(span);\r\n return {\r\n id: span.markerId ? `step-${span.markerId}` : `step-${i + 1}`,\r\n number: i + 1,\r\n title,\r\n ...(body && body !== title ? { body } : {}),\r\n timeUs,\r\n ...(highlight ? { highlight } : {}),\r\n ...(action ? { action } : {}),\r\n ...(span.markerId ? { markerId: span.markerId } : {}),\r\n };\r\n });\r\n const language = options.script?.language ?? options.transcript?.language;\r\n return { title: options.title, ...(options.intro ? { intro: options.intro } : {}), ...(language ? { language } : {}), steps };\r\n}\r\n","/**\r\n * Screenshots for a guide: each step's frame rendered by whatever still\r\n * renderer you have (`createRenderSession().renderStill` on a server,\r\n * `renderProjectStill` in the browser), with the step's control outlined,\r\n * the rest dimmed and the step number beside it. The annotation is an html\r\n * clip added to a throwaway copy of the project, so the renderer needs\r\n * nothing new and your project is untouched.\r\n */\r\nimport { applyCommands, createProject, type ProjectDocument } from \"@miraiclip/core\";\r\nimport type { Guide, GuideStep } from \"./build.js\";\r\n\r\n/** Render one frame of a document to image bytes (PNG/JPEG/WebP). */\r\nexport type StillRenderer = (doc: ProjectDocument, options: { timeUs: number; width?: number; height?: number }) => Promise<Uint8Array | Blob>;\r\n\r\nexport interface AnnotationStyle {\r\n /** Outline colour (default \"#facc15\"). */\r\n color?: string;\r\n /** Darken outside the box, 0..1 (default 0.35; 0 turns it off). */\r\n dim?: number;\r\n /** Show the step number (default true). */\r\n number?: boolean;\r\n}\r\n\r\nexport interface AddStillsOptions {\r\n /** Save one image and return its `src` (path or URL) as the guide will reference it. */\r\n store: (image: { data: Uint8Array | Blob; mimeType: string }, step: GuideStep) => Promise<string>;\r\n /** Output size (default: the project's). */\r\n width?: number;\r\n height?: number;\r\n /** Outline the step's control (default true). */\r\n annotate?: boolean | AnnotationStyle;\r\n /** MIME type the renderer produces (default \"image/png\"). */\r\n mimeType?: string;\r\n}\r\n\r\n/** The overlay markup (full-frame html clip) for one step. */\r\nexport function annotationTemplate(): string {\r\n return `<div style=\"position:relative;width:100%;height:100%;overflow:hidden;font-family:Inter,system-ui,sans-serif\">\r\n<div style=\"position:absolute;left:{{x}}%;top:{{y}}%;width:{{w}}%;height:{{h}}%;box-sizing:border-box;border:{{stroke}}px solid {{color}};border-radius:{{radius}}px;box-shadow:0 0 0 6000px rgba(0,0,0,{{dim}})\"></div>\r\n<div style=\"position:absolute;left:calc({{x}}% - {{badge}}px * .5);top:calc({{y}}% - {{badge}}px * .5);width:{{badge}}px;height:{{badge}}px;border-radius:50%;background:{{color}};color:#111;display:{{showNumber}};align-items:center;justify-content:center;font-weight:700;font-size:calc({{badge}}px * .55);box-shadow:0 2px 8px rgba(0,0,0,.4)\">{{n}}</div>\r\n</div>`;\r\n}\r\n\r\nfunction annotated(doc: ProjectDocument, step: GuideStep, style: AnnotationStyle): ProjectDocument {\r\n if (!step.highlight) return doc;\r\n const p = createProject(structuredClone(doc), { historyLimit: 1 });\r\n const h = step.highlight;\r\n const unit = Math.min(doc.settings.width, doc.settings.height) / 1080;\r\n const fps = doc.settings.fps || 30;\r\n const pct = (v: number) => Math.round(v * 100_000) / 1000;\r\n const r = applyCommands(p, [\r\n { type: \"track/add\", payload: { id: \"guide-annotation\", kind: \"video\" } },\r\n {\r\n type: \"clip/add\",\r\n payload: {\r\n kind: \"html\",\r\n id: \"guide-annotation\",\r\n trackId: \"guide-annotation\",\r\n startUs: Math.max(0, step.timeUs - Math.round(1e6 / fps)),\r\n durationUs: Math.round((2 * 1e6) / fps),\r\n template: annotationTemplate(),\r\n params: {\r\n x: pct(h.x),\r\n y: pct(h.y),\r\n w: pct(h.width),\r\n h: pct(h.height),\r\n stroke: Math.max(2, Math.round(5 * unit)),\r\n radius: Math.round(12 * unit),\r\n badge: Math.round(44 * unit),\r\n color: style.color ?? \"#facc15\",\r\n dim: style.dim ?? 0.35,\r\n showNumber: style.number === false ? \"none\" : \"flex\",\r\n n: step.number,\r\n },\r\n },\r\n },\r\n ]);\r\n if (!r.ok) throw new Error(`guide annotation failed: ${JSON.stringify(r.error)}`);\r\n return p.getState().doc;\r\n}\r\n\r\n/** Render, annotate and store a screenshot for every step; returns the guide with `image` set. */\r\nexport async function addStills(guide: Guide, doc: ProjectDocument, render: StillRenderer, options: AddStillsOptions): Promise<Guide> {\r\n const style: AnnotationStyle = typeof options.annotate === \"object\" ? options.annotate : {};\r\n const width = options.width ?? doc.settings.width;\r\n const height = options.height ?? Math.round((width * doc.settings.height) / doc.settings.width);\r\n const steps: GuideStep[] = [];\r\n for (const step of guide.steps) {\r\n const frameDoc = options.annotate === false ? doc : annotated(doc, step, style);\r\n const data = await render(frameDoc, { timeUs: step.timeUs, width, height });\r\n const src = await options.store({ data, mimeType: options.mimeType ?? \"image/png\" }, step);\r\n steps.push({ ...step, image: { src, width, height, alt: `Step ${step.number}: ${step.title}` } });\r\n }\r\n return { ...guide, steps };\r\n}\r\n","/** A guide as Markdown, standalone HTML, or JSON. */\r\nimport type { Guide } from \"./build.js\";\r\n\r\nexport interface MarkdownOptions {\r\n /** Prefix for image paths (e.g. \"images/\"). */\r\n imageBase?: string;\r\n /** Heading level for the title (default 1). */\r\n level?: 1 | 2;\r\n}\r\n\r\nconst mdEscape = (s: string) => s.replace(/([\\\\`*_[\\]<>])/g, \"\\\\$1\");\r\n\r\nexport function toMarkdown(guide: Guide, options: MarkdownOptions = {}): string {\r\n const h = \"#\".repeat(options.level ?? 1);\r\n const lines = [`${h} ${mdEscape(guide.title)}`, \"\"];\r\n if (guide.intro) lines.push(mdEscape(guide.intro), \"\");\r\n for (const step of guide.steps) {\r\n lines.push(`${h}# ${step.number}. ${mdEscape(step.title)}`, \"\");\r\n if (step.body) lines.push(mdEscape(step.body), \"\");\r\n if (step.image) lines.push(`![${mdEscape(step.image.alt)}](${encodeURI((options.imageBase ?? \"\") + step.image.src)})`, \"\");\r\n }\r\n return `${lines.join(\"\\n\").trimEnd()}\\n`;\r\n}\r\n\r\nexport interface HtmlOptions {\r\n imageBase?: string;\r\n /** Accent colour (default \"#2563eb\"). */\r\n accent?: string;\r\n /** A full page (default) or just the article element. */\r\n fragment?: boolean;\r\n}\r\n\r\nconst esc = (s: string) => s.replace(/[&<>\"']/g, (c) => ({ \"&\": \"&amp;\", \"<\": \"&lt;\", \">\": \"&gt;\", '\"': \"&quot;\", \"'\": \"&#39;\" })[c]!);\r\n\r\nexport function toHtml(guide: Guide, options: HtmlOptions = {}): string {\r\n const accent = options.accent ?? \"#2563eb\";\r\n const steps = guide.steps\r\n .map(\r\n (s) => ` <li class=\"mc-step\" id=\"${esc(s.id)}\">\r\n <h2><span class=\"mc-n\">${s.number}</span>${esc(s.title)}</h2>${s.body ? `\\n <p>${esc(s.body)}</p>` : \"\"}${\r\n s.image ? `\\n <img src=\"${esc((options.imageBase ?? \"\") + s.image.src)}\" width=\"${s.image.width}\" height=\"${s.image.height}\" alt=\"${esc(s.image.alt)}\" loading=\"lazy\">` : \"\"\r\n }\r\n </li>`,\r\n )\r\n .join(\"\\n\");\r\n const article = `<article class=\"mc-guide\"${guide.language ? ` lang=\"${esc(guide.language)}\"` : \"\"}>\r\n <h1>${esc(guide.title)}</h1>${guide.intro ? `\\n <p class=\"mc-intro\">${esc(guide.intro)}</p>` : \"\"}\r\n <ol class=\"mc-steps\">\r\n${steps}\r\n </ol>\r\n</article>`;\r\n if (options.fragment) return article;\r\n return `<!doctype html>\r\n<html${guide.language ? ` lang=\"${esc(guide.language)}\"` : \"\"}>\r\n<head>\r\n<meta charset=\"utf-8\">\r\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\r\n<title>${esc(guide.title)}</title>\r\n<style>\r\n :root { --accent: ${esc(accent)}; color-scheme: light dark; }\r\n body { margin: 0; font: 16px/1.6 system-ui, sans-serif; background: Canvas; color: CanvasText; }\r\n .mc-guide { max-width: 820px; margin: 0 auto; padding: 32px 16px 64px; }\r\n .mc-guide h1 { font-size: 2rem; line-height: 1.2; margin: 0 0 8px; }\r\n .mc-intro { opacity: .8; margin: 0 0 24px; }\r\n .mc-steps { list-style: none; padding: 0; margin: 0; }\r\n .mc-step { margin: 0 0 40px; }\r\n .mc-step h2 { display: flex; gap: 12px; align-items: center; font-size: 1.25rem; margin: 0 0 8px; }\r\n .mc-n { flex: none; display: inline-grid; place-items: center; width: 1.8em; height: 1.8em; border-radius: 50%; background: var(--accent); color: #fff; font-size: .9rem; }\r\n .mc-step p { margin: 0 0 12px; }\r\n .mc-step img { display: block; width: 100%; height: auto; border-radius: 10px; border: 1px solid color-mix(in srgb, CanvasText 15%, transparent); }\r\n</style>\r\n</head>\r\n<body>\r\n${article}\r\n</body>\r\n</html>\r\n`;\r\n}\r\n\r\nexport function toJson(guide: Guide): string {\r\n return `${JSON.stringify(guide, null, 2)}\\n`;\r\n}\r\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@miraiclip/guides",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Step-by-step written guides from a Miraiclip screencast: steps from markers or clicks, text from the narration or transcript, annotated screenshots from any still renderer, out as Markdown, HTML or JSON.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -37,16 +37,16 @@
37
37
  },
38
38
  "dependencies": {
39
39
  "@miraiclip/capture": "^0.1.0",
40
- "@miraiclip/screencast": "^0.1.0",
41
- "@miraiclip/transcript": "^0.1.0"
40
+ "@miraiclip/transcript": "^0.1.2",
41
+ "@miraiclip/screencast": "^0.1.2"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@types/node": "^22.20.1",
45
45
  "tsup": "^8.5.1",
46
46
  "typescript": "^5.9.3",
47
47
  "vitest": "^3.2.4",
48
- "@miraiclip/core": "^0.6.0",
49
- "@miraiclip/server-export": "^0.5.0"
48
+ "@miraiclip/core": "^0.8.0",
49
+ "@miraiclip/server-export": "^0.6.0"
50
50
  },
51
51
  "keywords": [
52
52
  "documentation",
@@ -57,7 +57,7 @@
57
57
  "miraiclip"
58
58
  ],
59
59
  "peerDependencies": {
60
- "@miraiclip/core": "^0.6.0"
60
+ "@miraiclip/core": "^0.8.0"
61
61
  },
62
62
  "scripts": {
63
63
  "build": "tsup",