scrolltube 2.1.0 ā 2.1.3
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/AGENTS.md +2 -1
- package/README.md +7 -6
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +240 -0
- package/dist/core/scrolltube.umd.min.js +1 -1
- package/dist/core/types.d.ts +1 -0
- package/dist/pipeline/browser-driver.d.ts +5 -0
- package/dist/pipeline/browser-driver.js +30 -1
- package/dist/pipeline/index.d.ts +1 -1
- package/dist/pipeline/index.js +76 -4
- package/dist/pipeline/node-driver.d.ts +6 -0
- package/dist/pipeline/node-driver.js +27 -1
- package/dist/pipeline/types.d.ts +5 -0
- package/dist/react/index.js +1 -1
- package/docs/ai-integration.md +4 -2
- package/docs/architecture.md +2 -0
- package/docs/asset-pipeline.md +13 -6
- package/package.json +2 -2
package/AGENTS.md
CHANGED
|
@@ -11,7 +11,8 @@ You are a ScrollTube Implementation Expert. Your goal is to transform static med
|
|
|
11
11
|
### Step 1: Asset Preparation (The CLI)
|
|
12
12
|
Before writing any UI code, you MUST process the raw assets (video or images) into a ScrollTube project.
|
|
13
13
|
```bash
|
|
14
|
-
npx
|
|
14
|
+
npx scrolltube create <input_path> --cloud --depth --track "main subject"
|
|
15
|
+
|
|
15
16
|
```
|
|
16
17
|
- **Why?** This generates optimized multi-resolution frames, optional AI-tracked subject coordinates, and optional depth maps.
|
|
17
18
|
- **Output**: A directory containing `scrolltube.json` and variant folders (`mobile/`, `desktop/`).
|
package/README.md
CHANGED
|
@@ -28,7 +28,8 @@ Transform your video into a ScrollTube project from your terminal:
|
|
|
28
28
|
|
|
29
29
|
```bash
|
|
30
30
|
# This will extract frames, track the subject, and generate optimized variants and depth maps.
|
|
31
|
-
npx
|
|
31
|
+
npx scrolltube create "your-video.mp4" --name "my-project" --track "apple" --cloud --depth
|
|
32
|
+
|
|
32
33
|
```
|
|
33
34
|
|
|
34
35
|
#### Programmatic Usage (Browser/Node)
|
|
@@ -58,9 +59,9 @@ const project = await pipeline.create({
|
|
|
58
59
|
All you have to do now, is to drop the scrolltube.json project into your ScrollTubeProvider.
|
|
59
60
|
|
|
60
61
|
#### Vanilla JS Integration
|
|
61
|
-
For full implementation, please refer to the [Vanilla JS Example](https://github.com/aleskozelsky/scrolltube/blob/main/
|
|
62
|
+
For full implementation, please refer to the [Vanilla JS Example](https://github.com/aleskozelsky/scrolltube/blob/main/demos/html/index.html).
|
|
62
63
|
|
|
63
|
-
[Live Demo](https://
|
|
64
|
+
[Live Demo](https://demo-html.scroll.tube)
|
|
64
65
|
|
|
65
66
|
```html
|
|
66
67
|
<!-- 2. Drop it into your HTML -->
|
|
@@ -75,9 +76,9 @@ For full implementation, please refer to the [Vanilla JS Example](https://github
|
|
|
75
76
|
|
|
76
77
|
|
|
77
78
|
#### React Integration
|
|
78
|
-
For full implementation, please refer to the [React Integration Example](https://github.com/aleskozelsky/scrolltube/blob/main/
|
|
79
|
+
For full implementation, please refer to the [React Integration Example](https://github.com/aleskozelsky/scrolltube/blob/main/demos/create-next-app/src/app/page.tsx).
|
|
79
80
|
|
|
80
|
-
[Live Demo](https://
|
|
81
|
+
[Live Demo](https://demo-nextjs.scroll.tube)
|
|
81
82
|
|
|
82
83
|
|
|
83
84
|
```tsx
|
|
@@ -133,7 +134,7 @@ Choose your path based on your role:
|
|
|
133
134
|
- [**React Hooks**](https://docs.scroll.tube/react-integration): Build custom interactive components.
|
|
134
135
|
|
|
135
136
|
### š¤ For AI Agents
|
|
136
|
-
- [**AGENTS.md**](https://github.com/aleskozelsky/scrolltube/blob/main/
|
|
137
|
+
- [**AGENTS.md**](https://github.com/aleskozelsky/scrolltube/blob/main/AGENTS.md): Technical standard operating procedures for the repository.
|
|
137
138
|
- [**AI Integration Protocol**](https://docs.scroll.tube/ai-integration): How to prompt agents to build scenes for you.
|
|
138
139
|
|
|
139
140
|
---
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
37
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
38
|
+
};
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
const commander_1 = require("commander");
|
|
41
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
42
|
+
const fs = __importStar(require("fs-extra"));
|
|
43
|
+
const path = __importStar(require("path"));
|
|
44
|
+
const child_process_1 = require("child_process");
|
|
45
|
+
const ffmpeg_static_1 = __importDefault(require("ffmpeg-static"));
|
|
46
|
+
const pipeline_1 = require("../pipeline");
|
|
47
|
+
const readline = __importStar(require("readline"));
|
|
48
|
+
require("dotenv/config");
|
|
49
|
+
const pkg = require('../../package.json');
|
|
50
|
+
/**
|
|
51
|
+
* MEDIA QUERY BUILDER
|
|
52
|
+
*/
|
|
53
|
+
function buildVariantsFromIds(input) {
|
|
54
|
+
const result = [];
|
|
55
|
+
const orientations = ['portrait', 'landscape'];
|
|
56
|
+
// 1. Process each "Target" resolution
|
|
57
|
+
input.forEach(item => {
|
|
58
|
+
let res = 0;
|
|
59
|
+
if (typeof item === 'number')
|
|
60
|
+
res = item;
|
|
61
|
+
else if (typeof item === 'string')
|
|
62
|
+
res = parseInt(item);
|
|
63
|
+
else if (item.height)
|
|
64
|
+
res = item.height; // Assume height is the defining metric
|
|
65
|
+
if (!res || isNaN(res))
|
|
66
|
+
return;
|
|
67
|
+
orientations.forEach(orient => {
|
|
68
|
+
const isPortrait = orient === 'portrait';
|
|
69
|
+
const width = isPortrait ? res : Math.round(res * (16 / 9));
|
|
70
|
+
const height = isPortrait ? Math.round(res * (16 / 9)) : res;
|
|
71
|
+
result.push({
|
|
72
|
+
id: `${res}p_${orient.substring(0, 1)}`,
|
|
73
|
+
width,
|
|
74
|
+
height,
|
|
75
|
+
orientation: orient,
|
|
76
|
+
aspectRatio: isPortrait ? '9:16' : '16:9',
|
|
77
|
+
media: `(orientation: ${orient})` // Minimal fallback
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
// 2. Sort by height (ascending) so the engine finds the first one that fits
|
|
82
|
+
return result.sort((a, b) => a.height - b.height);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* CONFIG LOADER
|
|
86
|
+
* Looks for scrolltube.config.js/ts in the current working directory.
|
|
87
|
+
*/
|
|
88
|
+
async function loadProjectConfig() {
|
|
89
|
+
const possiblePaths = [
|
|
90
|
+
path.join(process.cwd(), 'scrolltube.cli.config.js'),
|
|
91
|
+
path.join(process.cwd(), 'scrolltube.cli.config.cjs'),
|
|
92
|
+
path.join(process.cwd(), 'scrolltube.cli.config.ts')
|
|
93
|
+
];
|
|
94
|
+
for (const p of possiblePaths) {
|
|
95
|
+
if (fs.existsSync(p)) {
|
|
96
|
+
try {
|
|
97
|
+
// For simplicity in CLI we handle commonjs/esm basics
|
|
98
|
+
// If it's TS, it might need jiti or ts-node, but let's assume JS for now
|
|
99
|
+
// or use a simple dynamic import if supported.
|
|
100
|
+
return require(p);
|
|
101
|
+
}
|
|
102
|
+
catch (e) {
|
|
103
|
+
console.warn(chalk_1.default.yellow(`ā ļø Found config at ${p} but failed to load it. Skipping...`));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Robust FFmpeg Detection
|
|
111
|
+
* Prioritizes bundled static binary, then system PATH.
|
|
112
|
+
*/
|
|
113
|
+
function getFFmpegPath() {
|
|
114
|
+
// 1. Try bundled ffmpeg-static
|
|
115
|
+
if (ffmpeg_static_1.default)
|
|
116
|
+
return ffmpeg_static_1.default;
|
|
117
|
+
// 2. Try system PATH
|
|
118
|
+
try {
|
|
119
|
+
(0, child_process_1.execSync)('ffmpeg -version', { stdio: 'ignore' });
|
|
120
|
+
return 'ffmpeg';
|
|
121
|
+
}
|
|
122
|
+
catch (e) {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const program = new commander_1.Command();
|
|
127
|
+
program
|
|
128
|
+
.name('scrolltube')
|
|
129
|
+
.description('ScrollTube CLI - Immersive Web SDK')
|
|
130
|
+
.version(pkg.version);
|
|
131
|
+
/**
|
|
132
|
+
* Interactive Helper
|
|
133
|
+
*/
|
|
134
|
+
async function prompt(question, defaultValue) {
|
|
135
|
+
const rl = readline.createInterface({
|
|
136
|
+
input: process.stdin,
|
|
137
|
+
output: process.stdout
|
|
138
|
+
});
|
|
139
|
+
return new Promise(resolve => {
|
|
140
|
+
rl.question(`${chalk_1.default.cyan('?')} ${question}${defaultValue ? ` (${defaultValue})` : ''}: `, (answer) => {
|
|
141
|
+
rl.close();
|
|
142
|
+
resolve(answer.trim() || defaultValue || '');
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
program
|
|
147
|
+
.command('create')
|
|
148
|
+
.description('ONE-STEP: Transform video/images into a responsive ScrollTube')
|
|
149
|
+
.argument('[input]', 'Path to input video or directory of images')
|
|
150
|
+
.option('-o, --output <dir>', 'Output directory (deprecated, use --name)')
|
|
151
|
+
.option('-p, --track <text>', 'Text prompt for subject tracking', 'main subject')
|
|
152
|
+
.option('-n, --name <string>', 'Name of the project')
|
|
153
|
+
.option('-v, --variants <string>', 'Comma-separated target resolutions (e.g. 720,1080)')
|
|
154
|
+
.option('-s, --step <number>', 'Process every Nth frame (default: 1)', '1')
|
|
155
|
+
.option('--cloud', 'Use Fal.ai for tracking and refinement', false)
|
|
156
|
+
.option('--depth', 'Generate a 3D depth map for the displacement effect (Requires --cloud)', false)
|
|
157
|
+
.action(async (inputArg, opts) => {
|
|
158
|
+
console.log(chalk_1.default.bold.blue('\nšļø ScrollTube Asset Pipeline\n'));
|
|
159
|
+
// 0. PRE-FLIGHT CHECK
|
|
160
|
+
const ffmpegPath = getFFmpegPath();
|
|
161
|
+
if (!ffmpegPath) {
|
|
162
|
+
console.error(chalk_1.default.red('\nā FFmpeg not found!'));
|
|
163
|
+
console.log(chalk_1.default.yellow('This CLI requires FFmpeg to process videos.'));
|
|
164
|
+
console.log('Please install it manually or ensure regular npm install was successful.');
|
|
165
|
+
process.exit(1);
|
|
166
|
+
}
|
|
167
|
+
const projectConfig = await loadProjectConfig();
|
|
168
|
+
let input = inputArg;
|
|
169
|
+
let track = opts.track;
|
|
170
|
+
let projectName = opts.name;
|
|
171
|
+
let useTracking = opts.cloud; // Default to cloud if flag set
|
|
172
|
+
let useDepth = opts.depth;
|
|
173
|
+
let customVariants = projectConfig?.variants || (opts.variants ? buildVariantsFromIds(opts.variants.split(',')) : null);
|
|
174
|
+
// 1. INPUT VALIDATION (Immediate)
|
|
175
|
+
while (!input || !fs.existsSync(input)) {
|
|
176
|
+
if (input && !fs.existsSync(input)) {
|
|
177
|
+
console.error(chalk_1.default.red(`\nā Error: Input path "${input}" does not exist.`));
|
|
178
|
+
}
|
|
179
|
+
input = await prompt('Path to input video or directory of images');
|
|
180
|
+
}
|
|
181
|
+
// 2. PROJECT NAME & SETTINGS
|
|
182
|
+
if (!projectName) {
|
|
183
|
+
projectName = await prompt('Project name', 'scrolltube-project');
|
|
184
|
+
}
|
|
185
|
+
let step = parseInt(opts.step) || 1;
|
|
186
|
+
if (!inputArg) {
|
|
187
|
+
const stepInput = await prompt('Process every Nth frame (Step size)', '1');
|
|
188
|
+
step = parseInt(stepInput) || 1;
|
|
189
|
+
}
|
|
190
|
+
// AI Tracking logic preserved in CLI wrapper...
|
|
191
|
+
// ...
|
|
192
|
+
const pipeline = new pipeline_1.AssetPipeline({
|
|
193
|
+
apiKey: process.env.FAL_KEY,
|
|
194
|
+
onProgress: (p) => {
|
|
195
|
+
// You could add a progress bar here
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
try {
|
|
199
|
+
await pipeline.create({
|
|
200
|
+
input: input,
|
|
201
|
+
name: projectName,
|
|
202
|
+
track: useTracking ? track : undefined,
|
|
203
|
+
depth: useDepth,
|
|
204
|
+
variants: customVariants || [720, 1080],
|
|
205
|
+
step: step
|
|
206
|
+
});
|
|
207
|
+
console.log(chalk_1.default.bold.green(`\nā
Project Created Successfully!`));
|
|
208
|
+
console.log(chalk_1.default.white(`š Output: ${projectName}`));
|
|
209
|
+
console.log(chalk_1.default.white(`š Config: scrolltube.json`));
|
|
210
|
+
console.log(chalk_1.default.cyan(`\nNext: Import the .json into your <ScrollTubeProvider />\n`));
|
|
211
|
+
}
|
|
212
|
+
catch (err) {
|
|
213
|
+
console.error(chalk_1.default.red(`\nā Error during pipeline: ${err.message}`));
|
|
214
|
+
process.exit(1);
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
// NEW UPDATE COMMAND
|
|
218
|
+
program
|
|
219
|
+
.command('update')
|
|
220
|
+
.description('Rerun extraction and tracking on an existing project')
|
|
221
|
+
.argument('<dir>', 'Project directory')
|
|
222
|
+
.option('-p, --track <text>', 'Additional subject to track')
|
|
223
|
+
.action(async (dir, opts) => {
|
|
224
|
+
console.log(chalk_1.default.bold.yellow('\nā»ļø ScrollTube Update Pipeline\n'));
|
|
225
|
+
const projectPath = path.resolve(dir);
|
|
226
|
+
const configPath = path.join(projectPath, 'scrolltube.json');
|
|
227
|
+
if (!fs.existsSync(configPath)) {
|
|
228
|
+
console.error(chalk_1.default.red('ā Not a valid ScrollTube project directory (missing scrolltube.json).'));
|
|
229
|
+
process.exit(1);
|
|
230
|
+
}
|
|
231
|
+
const config = await fs.readJson(configPath);
|
|
232
|
+
if (config.version !== pkg.version) {
|
|
233
|
+
console.warn(chalk_1.default.yellow(`ā ļø Version Mismatch: Project is ${config.version}, CLI is ${pkg.version}`));
|
|
234
|
+
}
|
|
235
|
+
console.log(chalk_1.default.red('ā ļø UNDER CONSTRUCTION'));
|
|
236
|
+
console.log(chalk_1.default.yellow('The "update" command is currently being refactored for the Universal Pipeline.'));
|
|
237
|
+
console.log(chalk_1.default.dim('Please use "scft create" to regenerate your project for now.\n'));
|
|
238
|
+
process.exit(0);
|
|
239
|
+
});
|
|
240
|
+
program.parse(process.argv);
|