vueseq 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.md +40 -8
- package/bin/cli.js +31 -17
- package/package.json +2 -2
- package/src/renderer/render.js +45 -4
- package/src/runtime/gsap-bridge.js +11 -1
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<img src="./vueseq.svg" alt="VueSeq Logo"
|
|
2
|
+
<img src="./vueseq.svg" alt="VueSeq Logo" >
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
# VueSeq
|
|
@@ -26,6 +26,32 @@ https://github.com/user-attachments/assets/84d01c02-4b4f-4d86-879e-720a7e367967
|
|
|
26
26
|
|
|
27
27
|
*This video was rendered with VueSeq from [examples/HelloWorld.vue](./examples/HelloWorld.vue)*
|
|
28
28
|
|
|
29
|
+
## What Can You Build?
|
|
30
|
+
|
|
31
|
+
**Your app. As a video. Using your actual components.**
|
|
32
|
+
|
|
33
|
+
Install VueSeq into your Vue project and create stunning videos that perfectly match your brand—because they *are* your brand. No recreating UIs in After Effects. No screen recording artifacts. Just your components, animated with GSAP, rendered to pixel-perfect video.
|
|
34
|
+
|
|
35
|
+
### 🎬 App Showcases & Demos
|
|
36
|
+
Create promotional videos using your real UI components. Product tours, feature walkthroughs, "What's New in v2.0" announcements—all with your exact design system, your exact colors, your exact components.
|
|
37
|
+
|
|
38
|
+
### 📱 Social Media Content
|
|
39
|
+
Generate short-form videos for Twitter, LinkedIn, Instagram. Show off a feature in 15 seconds. Your followers see your actual product, not a mockup.
|
|
40
|
+
|
|
41
|
+
### 📚 Documentation & Tutorials
|
|
42
|
+
Animate how your features work. Show state transitions, user flows, component interactions. Embed videos in your docs that never go stale—regenerate them when your UI changes.
|
|
43
|
+
|
|
44
|
+
### 📊 Data Visualizations
|
|
45
|
+
Animated charts, dashboards, infographics. Watch your bar charts grow, your line graphs draw, your data come alive.
|
|
46
|
+
|
|
47
|
+
### 🎨 Design System Demos
|
|
48
|
+
Showcase your component library in motion. Let designers and developers *see* how components animate, transition, and interact.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
**The idea is simple:** If you can build it in Vue, you can render it to video. One command. Deterministic output. Every time.
|
|
53
|
+
|
|
54
|
+
|
|
29
55
|
## Philosophy
|
|
30
56
|
|
|
31
57
|
VueSeq is intentionally minimal. We bundle only the essentials: **Vue**, **GSAP**, **Playwright**, and **Vite**.
|
|
@@ -133,10 +159,10 @@ span {
|
|
|
133
159
|
### 2. Render to Video
|
|
134
160
|
|
|
135
161
|
```bash
|
|
136
|
-
npx vueseq
|
|
162
|
+
npx vueseq examples/HelloWorld.vue -o examples/hello.mp4
|
|
137
163
|
```
|
|
138
164
|
|
|
139
|
-
That's it! Your video will be rendered at 1920x1080, 30fps.
|
|
165
|
+
That's it! Duration is auto-detected from your timeline. Your video will be rendered at 1920x1080, 30fps.
|
|
140
166
|
|
|
141
167
|
## CLI Options
|
|
142
168
|
|
|
@@ -145,7 +171,7 @@ vueseq <Video.vue> [options]
|
|
|
145
171
|
|
|
146
172
|
Options:
|
|
147
173
|
-o, --output Output file (default: ./output.mp4)
|
|
148
|
-
-d, --duration Duration in seconds (
|
|
174
|
+
-d, --duration Duration in seconds (auto-detected from timeline if not specified)
|
|
149
175
|
-f, --fps Frames per second (default: 30)
|
|
150
176
|
-w, --width Video width in pixels (default: 1920)
|
|
151
177
|
-H, --height Video height in pixels (default: 1080)
|
|
@@ -155,14 +181,20 @@ Options:
|
|
|
155
181
|
### Examples
|
|
156
182
|
|
|
157
183
|
```bash
|
|
158
|
-
#
|
|
159
|
-
vueseq
|
|
184
|
+
# Simple example (auto-detects duration)
|
|
185
|
+
npx vueseq examples/HelloWorld.vue -o examples/hello.mp4
|
|
186
|
+
|
|
187
|
+
# Multi-scene showcase
|
|
188
|
+
npx vueseq examples/Showcase.vue -o examples/showcase.mp4
|
|
189
|
+
|
|
190
|
+
# Override duration (partial render)
|
|
191
|
+
npx vueseq examples/Showcase.vue -d 10 -o examples/partial.mp4
|
|
160
192
|
|
|
161
193
|
# 4K at 60fps
|
|
162
|
-
vueseq
|
|
194
|
+
npx vueseq examples/HelloWorld.vue -f 60 -w 3840 -H 2160 -o examples/hello-4k.mp4
|
|
163
195
|
|
|
164
196
|
# Square for social media
|
|
165
|
-
vueseq
|
|
197
|
+
npx vueseq examples/HelloWorld.vue -w 1080 -H 1080 -o examples/hello-square.mp4
|
|
166
198
|
```
|
|
167
199
|
|
|
168
200
|
## Programmatic API
|
package/bin/cli.js
CHANGED
|
@@ -26,15 +26,15 @@ USAGE:
|
|
|
26
26
|
|
|
27
27
|
OPTIONS:
|
|
28
28
|
-o, --output Output file (default: ./output.mp4)
|
|
29
|
-
-d, --duration Duration in seconds (
|
|
29
|
+
-d, --duration Duration in seconds (auto-detected if not specified)
|
|
30
30
|
-f, --fps Frames per second (default: 30)
|
|
31
31
|
-w, --width Video width in pixels (default: 1920)
|
|
32
32
|
-H, --height Video height in pixels (default: 1080)
|
|
33
33
|
--help Show this help message
|
|
34
34
|
|
|
35
35
|
EXAMPLE:
|
|
36
|
-
vueseq
|
|
37
|
-
vueseq
|
|
36
|
+
npx vueseq examples/HelloWorld.vue -o examples/hello.mp4
|
|
37
|
+
npx vueseq examples/Showcase.vue -w 1280 -H 720 -f 60 -o examples/showcase.mp4
|
|
38
38
|
`)
|
|
39
39
|
}
|
|
40
40
|
|
|
@@ -76,16 +76,18 @@ if (extname(inputPath) !== '.vue') {
|
|
|
76
76
|
process.exit(1)
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
//
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
process.exit(1)
|
|
83
|
-
}
|
|
79
|
+
// Parse duration if provided
|
|
80
|
+
let duration = null
|
|
81
|
+
let durationAuto = false
|
|
84
82
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
83
|
+
if (values.duration) {
|
|
84
|
+
duration = parseFloat(values.duration)
|
|
85
|
+
if (isNaN(duration) || duration <= 0) {
|
|
86
|
+
console.error('Error: Duration must be a positive number')
|
|
87
|
+
process.exit(1)
|
|
88
|
+
}
|
|
89
|
+
} else {
|
|
90
|
+
durationAuto = true
|
|
89
91
|
}
|
|
90
92
|
|
|
91
93
|
// Parse numeric options
|
|
@@ -104,13 +106,25 @@ if (isNaN(width) || width <= 0 || isNaN(height) || height <= 0) {
|
|
|
104
106
|
}
|
|
105
107
|
|
|
106
108
|
// Import renderer and start rendering
|
|
107
|
-
console.log(`\nVueSeq - Rendering ${input}`)
|
|
108
|
-
console.log(` Duration: ${duration}s at ${fps}fps (${Math.ceil(duration * fps)} frames)`)
|
|
109
|
-
console.log(` Resolution: ${width}x${height}`)
|
|
110
|
-
console.log(` Output: ${values.output}\n`)
|
|
111
|
-
|
|
112
109
|
try {
|
|
113
110
|
const { renderToMp4 } = await import('../src/renderer/encode.js')
|
|
111
|
+
const { getTimelineDuration } = await import('../src/renderer/render.js')
|
|
112
|
+
|
|
113
|
+
// Auto-detect duration if not provided
|
|
114
|
+
if (durationAuto) {
|
|
115
|
+
console.log(`\nVueSeq - Detecting timeline duration...`)
|
|
116
|
+
duration = await getTimelineDuration({ input: inputPath, width, height })
|
|
117
|
+
if (!duration || duration <= 0) {
|
|
118
|
+
console.error('Error: Could not auto-detect duration. Use -d to specify manually.')
|
|
119
|
+
process.exit(1)
|
|
120
|
+
}
|
|
121
|
+
console.log(` Auto-detected: ${duration.toFixed(2)}s`)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
console.log(`\nVueSeq - Rendering ${input}`)
|
|
125
|
+
console.log(` Duration: ${duration}s at ${fps}fps (${Math.ceil(duration * fps)} frames)${durationAuto ? ' (auto)' : ''}`)
|
|
126
|
+
console.log(` Resolution: ${width}x${height}`)
|
|
127
|
+
console.log(` Output: ${values.output}\n`)
|
|
114
128
|
|
|
115
129
|
const startTime = Date.now()
|
|
116
130
|
let lastLoggedPercent = -1
|
package/package.json
CHANGED
package/src/renderer/render.js
CHANGED
|
@@ -13,19 +13,56 @@ import { createVideoServer } from '../bundler/vite.js'
|
|
|
13
13
|
import { mkdir } from 'fs/promises'
|
|
14
14
|
import { join } from 'path'
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Get the timeline duration from a Vue component
|
|
18
|
+
* @param {Object} options
|
|
19
|
+
* @param {string} options.input - Absolute path to the Video.vue component
|
|
20
|
+
* @param {number} [options.width=1920] - Video width in pixels
|
|
21
|
+
* @param {number} [options.height=1080] - Video height in pixels
|
|
22
|
+
* @returns {Promise<number|null>} Duration in seconds, or null if not detectable
|
|
23
|
+
*/
|
|
24
|
+
export async function getTimelineDuration(options) {
|
|
25
|
+
const { input, width = 1920, height = 1080 } = options
|
|
26
|
+
|
|
27
|
+
const { url, cleanup } = await createVideoServer({ input, width, height })
|
|
28
|
+
|
|
29
|
+
const browser = await chromium.launch({ headless: true })
|
|
30
|
+
const context = await browser.newContext({
|
|
31
|
+
viewport: { width, height },
|
|
32
|
+
deviceScaleFactor: 1
|
|
33
|
+
})
|
|
34
|
+
const page = await context.newPage()
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
await page.goto(url, { waitUntil: 'networkidle' })
|
|
38
|
+
await page.waitForFunction(
|
|
39
|
+
() => window.__VUESEQ_READY__ === true,
|
|
40
|
+
{ timeout: 30000 }
|
|
41
|
+
)
|
|
42
|
+
// Give Vue/GSAP a moment to set up timelines
|
|
43
|
+
await page.waitForTimeout(100)
|
|
44
|
+
|
|
45
|
+
const duration = await page.evaluate(() => window.__VUESEQ_GET_DURATION__?.())
|
|
46
|
+
return duration
|
|
47
|
+
} finally {
|
|
48
|
+
await browser.close()
|
|
49
|
+
await cleanup()
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
16
53
|
/**
|
|
17
54
|
* Render frames from a Vue component
|
|
18
55
|
* @param {Object} options
|
|
19
56
|
* @param {string} options.input - Absolute path to the Video.vue component
|
|
20
57
|
* @param {number} [options.fps=30] - Frames per second
|
|
21
|
-
* @param {number} options.duration - Duration in seconds
|
|
58
|
+
* @param {number} [options.duration] - Duration in seconds (auto-detected if not provided)
|
|
22
59
|
* @param {number} [options.width=1920] - Video width in pixels
|
|
23
60
|
* @param {number} [options.height=1080] - Video height in pixels
|
|
24
61
|
* @param {function} [options.onProgress] - Progress callback
|
|
25
62
|
* @returns {Promise<{framesDir: string, totalFrames: number, cleanup: () => Promise<void>}>}
|
|
26
63
|
*/
|
|
27
64
|
export async function renderFrames(options) {
|
|
28
|
-
|
|
65
|
+
let {
|
|
29
66
|
input,
|
|
30
67
|
fps = 30,
|
|
31
68
|
duration,
|
|
@@ -34,8 +71,12 @@ export async function renderFrames(options) {
|
|
|
34
71
|
onProgress
|
|
35
72
|
} = options
|
|
36
73
|
|
|
37
|
-
|
|
38
|
-
|
|
74
|
+
// Auto-detect duration if not provided
|
|
75
|
+
if (!duration) {
|
|
76
|
+
duration = await getTimelineDuration({ input, width, height })
|
|
77
|
+
if (!duration || duration <= 0) {
|
|
78
|
+
throw new Error('Could not auto-detect duration. Specify -d/--duration manually.')
|
|
79
|
+
}
|
|
39
80
|
}
|
|
40
81
|
|
|
41
82
|
const totalFrames = Math.ceil(duration * fps)
|
|
@@ -35,7 +35,17 @@ window.__VUESEQ_SET_CONFIG__ = (config) => {
|
|
|
35
35
|
window.__VUESEQ_CONFIG__ = config
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
// 6.
|
|
38
|
+
// 6. Expose timeline duration for auto-detection
|
|
39
|
+
window.__VUESEQ_GET_DURATION__ = () => {
|
|
40
|
+
const duration = gsap.globalTimeline.duration()
|
|
41
|
+
// Return null for infinite timelines (repeat: -1)
|
|
42
|
+
if (duration === Infinity || duration > 3600) {
|
|
43
|
+
return null
|
|
44
|
+
}
|
|
45
|
+
return duration
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 7. Mark as ready after a microtask to ensure Vue is mounted
|
|
39
49
|
queueMicrotask(() => {
|
|
40
50
|
window.__VUESEQ_READY__ = true
|
|
41
51
|
})
|