clipwise 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/README.ko.md ADDED
@@ -0,0 +1,264 @@
1
+ [English](./README.md) | [한국어](./README.ko.md)
2
+
3
+ # Clipwise
4
+
5
+ YAML 시나리오를 작성하면 시네마틱 데모 영상(MP4/GIF)을 자동으로 만들어주는 스크린 레코더. Playwright CDP 기반.
6
+
7
+ <p align="center">
8
+ <img src="https://github.com/kwakseongjae/clipwise/releases/download/v0.1.0/demo.gif" width="100%" alt="Clipwise Demo" />
9
+ </p>
10
+
11
+ > *`npx clipwise demo` 한 줄로 생성된 영상입니다.*
12
+
13
+ ## 빠른 시작
14
+
15
+ ```bash
16
+ # 설치
17
+ npm install -D clipwise
18
+
19
+ # 내장 데모 즉시 실행
20
+ npx clipwise demo
21
+
22
+ # 또는 직접 시나리오 작성
23
+ npx clipwise init # clipwise.yaml 템플릿 생성
24
+ # clipwise.yaml 편집 — URL을 내 사이트로 변경
25
+ npx clipwise record clipwise.yaml -f mp4 # 녹화!
26
+ ```
27
+
28
+ ## 요구사항
29
+
30
+ - **Node.js** >= 18
31
+ - **ffmpeg** (MP4 출력용)
32
+ - **Chromium** (첫 실행 시 Playwright가 자동 설치)
33
+
34
+ ```bash
35
+ # macOS
36
+ brew install ffmpeg
37
+
38
+ # Ubuntu
39
+ sudo apt install ffmpeg
40
+
41
+ # Windows
42
+ choco install ffmpeg
43
+ ```
44
+
45
+ ## 사용법
46
+
47
+ ### CLI 명령어
48
+
49
+ ```bash
50
+ # 즉시 데모 — 내장 대시보드 녹화
51
+ npx clipwise demo # 브라우저 프레임, MP4
52
+ npx clipwise demo --device iphone # iPhone 목업
53
+ npx clipwise demo --device android # Android 목업
54
+ npx clipwise demo --device ipad # iPad 목업
55
+ npx clipwise demo --url https://my-app.com # 내 사이트 데모
56
+
57
+ # YAML 시나리오로 녹화
58
+ npx clipwise record <scenario.yaml> -f mp4 -o ./output
59
+ npx clipwise record <scenario.yaml> -f gif -o ./output
60
+
61
+ # 템플릿 초기화
62
+ npx clipwise init
63
+
64
+ # 녹화 없이 검증만
65
+ npx clipwise validate <scenario.yaml>
66
+ ```
67
+
68
+ ### 프로그래밍 API
69
+
70
+ ```typescript
71
+ import { ClipwiseRecorder, CanvasRenderer, encodeMp4, loadScenario } from "clipwise";
72
+
73
+ const scenario = await loadScenario("my-scenario.yaml");
74
+ const recorder = new ClipwiseRecorder();
75
+ const session = await recorder.record(scenario);
76
+
77
+ const renderer = new CanvasRenderer(scenario.effects, scenario.output, scenario.steps);
78
+ const frames = await renderer.composeAll(session.frames);
79
+
80
+ const mp4 = await encodeMp4(frames, scenario.output);
81
+ ```
82
+
83
+ ## YAML 시나리오 형식
84
+
85
+ 시나리오는 4개 섹션으로 구성됩니다: 메타데이터, 이펙트, 출력, 스텝.
86
+
87
+ ```yaml
88
+ name: "My Demo"
89
+ description: "선택 설명"
90
+
91
+ viewport:
92
+ width: 1280 # 브라우저 너비 (기본: 1280)
93
+ height: 800 # 브라우저 높이 (기본: 800)
94
+
95
+ effects:
96
+ # 아래 "이펙트" 섹션 참조
97
+
98
+ output:
99
+ format: mp4 # gif | mp4 | png-sequence
100
+ width: 1280
101
+ height: 800
102
+ fps: 30 # 1-60
103
+ quality: 80 # 1-100 (MP4: CRF에 매핑)
104
+
105
+ steps:
106
+ - name: "스텝 이름"
107
+ actions:
108
+ - action: navigate
109
+ url: "https://example.com"
110
+ captureDelay: 200 # 액션 후 대기(ms)
111
+ holdDuration: 800 # 결과 화면 유지(ms)
112
+ transition: none # none | fade
113
+ ```
114
+
115
+ ### 액션
116
+
117
+ | 액션 | 파라미터 | 설명 |
118
+ |------|---------|------|
119
+ | `navigate` | `url`, `waitUntil?` | URL로 이동 |
120
+ | `click` | `selector`, `delay?` | 요소 클릭 |
121
+ | `type` | `selector`, `text`, `delay?` | 텍스트 입력 (한 글자씩) |
122
+ | `hover` | `selector` | 요소에 마우스 올리기 |
123
+ | `scroll` | `y?`, `x?`, `selector?`, `smooth?` | 스크롤 |
124
+ | `wait` | `duration` | 대기 (ms) |
125
+ | `screenshot` | `name?`, `fullPage?` | 캡처 마커 |
126
+
127
+ ### 타이밍 팁
128
+
129
+ 빠른 데모 (~30초):
130
+ - `captureDelay: 50-100` ms
131
+ - `holdDuration: 500-800` ms
132
+ - `type.delay: 15-25` ms/글자
133
+
134
+ 느린 시네마틱:
135
+ - `captureDelay: 200-400` ms
136
+ - `holdDuration: 1500-2500` ms
137
+ - `type.delay: 40-60` ms/글자
138
+
139
+ ## 이펙트
140
+
141
+ 모든 이펙트는 선택사항이며 합리적인 기본값이 있습니다.
142
+
143
+ ### 줌
144
+
145
+ 적응형 줌 — 커서를 따라가며 클릭 대상에 자동 줌인.
146
+
147
+ ```yaml
148
+ zoom:
149
+ enabled: true
150
+ scale: 1.8 # 최대 줌 (1-5)
151
+ duration: 500 # 애니메이션 ms
152
+ ```
153
+
154
+ ### 커서
155
+
156
+ 커스텀 커서 + 클릭 리플 + 트레일 + 하이라이트 + 속도 조절.
157
+
158
+ ```yaml
159
+ cursor:
160
+ enabled: true
161
+ size: 20
162
+ speed: "fast" # fast (~72ms) | normal (~144ms) | slow (~288ms)
163
+ clickEffect: true
164
+ trail: true
165
+ highlight: true
166
+ ```
167
+
168
+ ### 배경
169
+
170
+ 그라디언트/단색 패딩 + 라운드 코너 + 그림자.
171
+
172
+ ```yaml
173
+ background:
174
+ type: gradient
175
+ value: "linear-gradient(135deg, #667eea 0%, #764ba2 100%)"
176
+ padding: 48
177
+ borderRadius: 14
178
+ shadow: true
179
+ ```
180
+
181
+ ### 디바이스 프레임
182
+
183
+ 녹화를 디바이스 목업으로 감싸기.
184
+
185
+ ```yaml
186
+ deviceFrame:
187
+ enabled: true
188
+ type: browser # browser | iphone | ipad | android | none
189
+ darkMode: true
190
+ ```
191
+
192
+ | 타입 | 설명 |
193
+ |------|------|
194
+ | `browser` | macOS 브라우저 크롬 (신호등 버튼) |
195
+ | `iphone` | iPhone 15 Pro (Dynamic Island + 홈바) |
196
+ | `ipad` | iPad Pro (전면 카메라) |
197
+ | `android` | Android (펀치홀 카메라) |
198
+
199
+ ### 키스트로크 HUD
200
+
201
+ 타이핑 내용을 화면 하단에 표시.
202
+
203
+ ```yaml
204
+ keystroke:
205
+ enabled: true
206
+ position: bottom-center
207
+ fontSize: 16
208
+ ```
209
+
210
+ ### 워터마크
211
+
212
+ 코너에 텍스트 오버레이.
213
+
214
+ ```yaml
215
+ watermark:
216
+ enabled: true
217
+ text: "Clipwise"
218
+ position: bottom-right
219
+ opacity: 0.5
220
+ ```
221
+
222
+ ### 속도 램프
223
+
224
+ 클릭 근처 슬로우모션, 유휴 구간 빨리감기.
225
+
226
+ ```yaml
227
+ speedRamp:
228
+ enabled: true
229
+ idleSpeed: 3.0
230
+ actionSpeed: 0.8
231
+ ```
232
+
233
+ ## AI로 시나리오 작성
234
+
235
+ [PROMPTS.md](./PROMPTS.md)에 바로 사용할 수 있는 AI 프롬프트 템플릿이 있습니다. ChatGPT나 Claude에 복붙하고 내 사이트 URL만 넣으면 YAML 시나리오를 생성해줍니다.
236
+
237
+ ## GitHub Pages
238
+
239
+ `docs/` 폴더에 문서 사이트와 라이브 데모 대시보드가 포함되어 있습니다:
240
+
241
+ 1. GitHub에 push: `git push origin main`
242
+ 2. **Settings > Pages** > source: `main`, folder: `/docs`
243
+ 3. 문서: `https://username.github.io/clipwise/`
244
+ 4. 데모: `https://username.github.io/clipwise/demo/`
245
+
246
+ ## 보안
247
+
248
+ - **셀렉터 검증**: YAML의 CSS 셀렉터는 안전한 문자만 허용
249
+ - **URL 처리**: `http://`, `https://`, `file://` 스키마만 허용
250
+ - **Chromium 샌드박스**: Playwright 기본 샌드박싱 적용
251
+ - **로컬 처리**: 녹화 프레임은 절대 외부로 전송되지 않음
252
+
253
+ ## 개발
254
+
255
+ ```bash
256
+ npm install # 의존성 설치
257
+ npm run build # tsup으로 빌드
258
+ npm run typecheck # 타입 체크
259
+ npm test # 테스트 (vitest)
260
+ ```
261
+
262
+ ## 라이선스
263
+
264
+ MIT
package/README.md CHANGED
@@ -1,7 +1,15 @@
1
+ [English](./README.md) | [한국어](./README.ko.md)
2
+
1
3
  # Clipwise
2
4
 
3
5
  Scriptable cinematic screen recorder for product demos — YAML in, polished MP4 out. Powered by Playwright CDP.
4
6
 
7
+ <p align="center">
8
+ <img src="https://github.com/kwakseongjae/clipwise/releases/download/v0.1.0/demo.gif" width="100%" alt="Clipwise Demo" />
9
+ </p>
10
+
11
+ > *Generated with `npx clipwise demo` — zero config, one command.*
12
+
5
13
  ## Quick Start
6
14
 
7
15
  ```bash
@@ -323,19 +331,24 @@ npx clipwise record my-scenario.yaml -f mp4 -o ./output
323
331
  - Keep `type.delay` at 15-25ms for a fast but readable typing effect
324
332
  - Use `transition: fade` between major sections for cinematic cuts
325
333
 
326
- ## Hosting the Demo Site (GitHub Pages)
334
+ ### Writing Scenarios with AI
335
+
336
+ See [PROMPTS.md](./PROMPTS.md) for a ready-to-use prompt template. Copy-paste it to ChatGPT or Claude with your site URL, and get a working YAML scenario back.
337
+
338
+ ## GitHub Pages
327
339
 
328
- Clipwise includes a demo dashboard in `docs/index.html`. To host it:
340
+ Clipwise includes a documentation site and a live demo dashboard in the `docs/` folder. To host it:
329
341
 
330
342
  1. Push to GitHub: `git push origin main`
331
343
  2. Go to **Settings > Pages**
332
344
  3. Set source to **Deploy from a branch**, select `main`, folder `/docs`
333
- 4. Demo goes live at `https://kwakseongjae.github.io/clipwise/`
345
+ 4. Docs go live at `https://kwakseongjae.github.io/clipwise/`
346
+ 5. Demo dashboard at `https://kwakseongjae.github.io/clipwise/demo/`
334
347
 
335
348
  Then anyone can record the demo site:
336
349
 
337
350
  ```bash
338
- npx clipwise demo --url https://kwakseongjae.github.io/clipwise/
351
+ npx clipwise demo --url https://kwakseongjae.github.io/clipwise/demo/
339
352
  ```
340
353
 
341
354
  ## Security
package/dist/cli/index.js CHANGED
@@ -373,6 +373,7 @@ var ClipwiseRecorder = class {
373
373
  isCapturing = false;
374
374
  targetFps = 30;
375
375
  cursorSpeed = "fast";
376
+ firstContentTimestamp = 0;
376
377
  /**
377
378
  * Launch the browser and create a page with the scenario viewport.
378
379
  */
@@ -395,6 +396,7 @@ var ClipwiseRecorder = class {
395
396
  this.currentStepIndex = 0;
396
397
  this.cursorPosition = { x: 0, y: 0 };
397
398
  this.isCapturing = false;
399
+ this.firstContentTimestamp = 0;
398
400
  }
399
401
  /**
400
402
  * Start CDP screencast for continuous frame capture.
@@ -534,6 +536,13 @@ var ClipwiseRecorder = class {
534
536
  switch (action.action) {
535
537
  case "navigate": {
536
538
  await this.page.goto(action.url, { waitUntil: action.waitUntil });
539
+ await this.page.evaluate(
540
+ () => new Promise((r) => requestAnimationFrame(() => requestAnimationFrame(r)))
541
+ ).catch(() => {
542
+ });
543
+ if (this.firstContentTimestamp === 0) {
544
+ this.firstContentTimestamp = Date.now();
545
+ }
537
546
  await this.waitWithRepaints(300);
538
547
  break;
539
548
  }
@@ -654,7 +663,10 @@ var ClipwiseRecorder = class {
654
663
  */
655
664
  buildCapturedFrames() {
656
665
  if (this.rawFrames.length === 0) return [];
657
- return this.rawFrames.map((raw, index) => {
666
+ const contentStart = this.firstContentTimestamp;
667
+ const trimmed = contentStart > 0 ? this.rawFrames.filter((f) => f.timestamp >= contentStart) : this.rawFrames;
668
+ if (trimmed.length === 0) return [];
669
+ return trimmed.map((raw, index) => {
658
670
  const cursorPos = this.interpolateCursorAt(raw.timestamp);
659
671
  const clickEvent = this.clickTimeline.find(
660
672
  (click) => raw.timestamp >= click.timestamp && raw.timestamp <= click.timestamp + CLICK_EFFECT_DURATION_MS
package/dist/index.d.ts CHANGED
@@ -1402,6 +1402,7 @@ declare class ClipwiseRecorder {
1402
1402
  private isCapturing;
1403
1403
  private targetFps;
1404
1404
  private cursorSpeed;
1405
+ private firstContentTimestamp;
1405
1406
  /**
1406
1407
  * Launch the browser and create a page with the scenario viewport.
1407
1408
  */
package/dist/index.js CHANGED
@@ -76,6 +76,7 @@ var ClipwiseRecorder = class {
76
76
  isCapturing = false;
77
77
  targetFps = 30;
78
78
  cursorSpeed = "fast";
79
+ firstContentTimestamp = 0;
79
80
  /**
80
81
  * Launch the browser and create a page with the scenario viewport.
81
82
  */
@@ -98,6 +99,7 @@ var ClipwiseRecorder = class {
98
99
  this.currentStepIndex = 0;
99
100
  this.cursorPosition = { x: 0, y: 0 };
100
101
  this.isCapturing = false;
102
+ this.firstContentTimestamp = 0;
101
103
  }
102
104
  /**
103
105
  * Start CDP screencast for continuous frame capture.
@@ -237,6 +239,13 @@ var ClipwiseRecorder = class {
237
239
  switch (action.action) {
238
240
  case "navigate": {
239
241
  await this.page.goto(action.url, { waitUntil: action.waitUntil });
242
+ await this.page.evaluate(
243
+ () => new Promise((r) => requestAnimationFrame(() => requestAnimationFrame(r)))
244
+ ).catch(() => {
245
+ });
246
+ if (this.firstContentTimestamp === 0) {
247
+ this.firstContentTimestamp = Date.now();
248
+ }
240
249
  await this.waitWithRepaints(300);
241
250
  break;
242
251
  }
@@ -357,7 +366,10 @@ var ClipwiseRecorder = class {
357
366
  */
358
367
  buildCapturedFrames() {
359
368
  if (this.rawFrames.length === 0) return [];
360
- return this.rawFrames.map((raw, index) => {
369
+ const contentStart = this.firstContentTimestamp;
370
+ const trimmed = contentStart > 0 ? this.rawFrames.filter((f) => f.timestamp >= contentStart) : this.rawFrames;
371
+ if (trimmed.length === 0) return [];
372
+ return trimmed.map((raw, index) => {
361
373
  const cursorPos = this.interpolateCursorAt(raw.timestamp);
362
374
  const clickEvent = this.clickTimeline.find(
363
375
  (click) => raw.timestamp >= click.timestamp && raw.timestamp <= click.timestamp + CLICK_EFFECT_DURATION_MS
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clipwise",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Scriptable cinematic screen recorder for product demos — YAML in, polished MP4 out",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",