@scenerok/cli 1.0.0 → 1.0.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 +42 -18
- package/dist/commands/cache.d.ts +3 -0
- package/dist/commands/cache.d.ts.map +1 -0
- package/dist/commands/cache.js +65 -0
- package/dist/commands/project.d.ts +3 -0
- package/dist/commands/project.d.ts.map +1 -0
- package/dist/commands/project.js +159 -0
- package/dist/commands/render.d.ts +1 -0
- package/dist/commands/render.d.ts.map +1 -1
- package/dist/commands/render.js +71 -40
- package/dist/index.js +13 -2
- package/dist/lib/api.d.ts +43 -1
- package/dist/lib/api.d.ts.map +1 -1
- package/dist/lib/api.js +59 -8
- package/dist/lib/config.d.ts +2 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +11 -0
- package/dist/lib/project.d.ts +14 -0
- package/dist/lib/project.d.ts.map +1 -0
- package/dist/lib/project.js +97 -0
- package/package.json +2 -1
- package/skills/aider/SKILL.md +171 -0
- package/skills/aider/vidscript-guide.md +355 -0
- package/skills/aider/vidscript-sample.md +21 -0
- package/skills/claude/SKILL.md +171 -0
- package/skills/claude/vidscript-guide.md +356 -0
- package/skills/claude/vidscript-sample.md +21 -0
- package/skills/codex/SKILL.md +171 -0
- package/skills/codex/vidscript-guide.md +355 -0
- package/skills/codex/vidscript-sample.md +21 -0
- package/skills/cursor/SKILL.md +171 -0
- package/skills/cursor/vidscript-guide.md +355 -0
- package/skills/cursor/vidscript-sample.md +21 -0
- package/skills/opencode/SKILL.md +171 -0
- package/skills/opencode/vidscript-guide.md +355 -0
- package/skills/opencode/vidscript-sample.md +21 -0
package/dist/lib/api.js
CHANGED
|
@@ -10,18 +10,22 @@ class ApiError extends Error {
|
|
|
10
10
|
this.name = 'ApiError';
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
|
-
|
|
14
|
-
const baseUrl = getBaseUrl();
|
|
13
|
+
function getAuthHeaders(contentType = 'application/json') {
|
|
15
14
|
const token = getApiToken();
|
|
16
|
-
const headers = {
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
const headers = {};
|
|
16
|
+
if (contentType) {
|
|
17
|
+
headers['Content-Type'] = contentType;
|
|
18
|
+
}
|
|
19
19
|
if (token) {
|
|
20
20
|
headers['Authorization'] = `Bearer ${token}`;
|
|
21
21
|
}
|
|
22
|
+
return headers;
|
|
23
|
+
}
|
|
24
|
+
async function apiCall(method, path, body) {
|
|
25
|
+
const baseUrl = getBaseUrl();
|
|
22
26
|
const response = await fetch(`${baseUrl}${path}`, {
|
|
23
27
|
method,
|
|
24
|
-
headers,
|
|
28
|
+
headers: getAuthHeaders(),
|
|
25
29
|
body: body ? JSON.stringify(body) : undefined,
|
|
26
30
|
});
|
|
27
31
|
const data = await response.json().catch(() => ({}));
|
|
@@ -30,15 +34,62 @@ async function apiCall(method, path, body) {
|
|
|
30
34
|
}
|
|
31
35
|
return data;
|
|
32
36
|
}
|
|
37
|
+
async function apiFormCall(path, formData) {
|
|
38
|
+
const baseUrl = getBaseUrl();
|
|
39
|
+
const headers = getAuthHeaders('');
|
|
40
|
+
const response = await fetch(`${baseUrl}${path}`, {
|
|
41
|
+
method: 'POST',
|
|
42
|
+
headers,
|
|
43
|
+
body: formData,
|
|
44
|
+
});
|
|
45
|
+
const data = await response.json().catch(() => ({}));
|
|
46
|
+
if (!response.ok) {
|
|
47
|
+
throw new ApiError(data.error || `HTTP ${response.status}`, response.status, data);
|
|
48
|
+
}
|
|
49
|
+
return data;
|
|
50
|
+
}
|
|
33
51
|
export async function validateVidscript(vidscript) {
|
|
34
52
|
return apiCall('POST', '/api/cli/validate', { vidscript });
|
|
35
53
|
}
|
|
36
|
-
export async function submitRender(vidscript, resolution) {
|
|
37
|
-
return apiCall('POST', '/api/cli/render', { vidscript, resolution });
|
|
54
|
+
export async function submitRender(vidscript, resolution, templateId) {
|
|
55
|
+
return apiCall('POST', '/api/cli/render', { vidscript, resolution, templateId });
|
|
38
56
|
}
|
|
39
57
|
export async function getRenderStatus(renderId) {
|
|
40
58
|
return apiCall('GET', `/api/cli/status?id=${renderId}`);
|
|
41
59
|
}
|
|
60
|
+
export async function downloadRender(renderId) {
|
|
61
|
+
const baseUrl = getBaseUrl();
|
|
62
|
+
const response = await fetch(`${baseUrl}/api/cli/render/download?id=${renderId}`, {
|
|
63
|
+
method: 'GET',
|
|
64
|
+
headers: getAuthHeaders(''),
|
|
65
|
+
});
|
|
66
|
+
if (!response.ok) {
|
|
67
|
+
const data = await response.json().catch(() => ({}));
|
|
68
|
+
throw new ApiError(data.error || `HTTP ${response.status}`, response.status, data);
|
|
69
|
+
}
|
|
70
|
+
const disposition = response.headers.get('content-disposition') || '';
|
|
71
|
+
const filenameMatch = disposition.match(/filename="([^"]+)"/);
|
|
72
|
+
const filename = filenameMatch?.[1] || `${renderId}.mp4`;
|
|
73
|
+
return {
|
|
74
|
+
buffer: Buffer.from(await response.arrayBuffer()),
|
|
75
|
+
filename,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
export async function uploadProject(formData) {
|
|
79
|
+
return apiFormCall('/api/cli/projects', formData);
|
|
80
|
+
}
|
|
81
|
+
export async function listProjects() {
|
|
82
|
+
return apiCall('GET', '/api/cli/projects');
|
|
83
|
+
}
|
|
84
|
+
export async function getAssetCache(options = {}) {
|
|
85
|
+
const params = new URLSearchParams();
|
|
86
|
+
if (options.plugin)
|
|
87
|
+
params.set('plugin', options.plugin);
|
|
88
|
+
if (options.limit)
|
|
89
|
+
params.set('limit', String(options.limit));
|
|
90
|
+
const suffix = params.toString() ? `?${params.toString()}` : '';
|
|
91
|
+
return apiCall('GET', `/api/cli/asset-cache${suffix}`);
|
|
92
|
+
}
|
|
42
93
|
export async function initiateDeviceAuth(deviceName) {
|
|
43
94
|
return apiCall('POST', '/api/cli/auth/device', { deviceName });
|
|
44
95
|
}
|
package/dist/lib/config.d.ts
CHANGED
|
@@ -8,4 +8,6 @@ export declare function writeConfig(config: CliConfig): void;
|
|
|
8
8
|
export declare function getBaseUrl(): string;
|
|
9
9
|
export declare function getApiToken(): string | undefined;
|
|
10
10
|
export declare function isAuthenticated(): boolean;
|
|
11
|
+
export declare function getConfigDir(): string;
|
|
12
|
+
export declare function getCacheDir(): string;
|
|
11
13
|
//# sourceMappingURL=config.d.ts.map
|
package/dist/lib/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAQD,wBAAgB,UAAU,IAAI,SAAS,CAUtC;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,SAAS,QAG5C;AAED,wBAAgB,UAAU,IAAI,MAAM,CAGnC;AAED,wBAAgB,WAAW,IAAI,MAAM,GAAG,SAAS,CAGhD;AAED,wBAAgB,eAAe,IAAI,OAAO,CAEzC;AAED,wBAAgB,YAAY,IAAI,MAAM,CAGrC;AAED,wBAAgB,WAAW,IAAI,MAAM,CAKpC"}
|
package/dist/lib/config.js
CHANGED
|
@@ -3,6 +3,7 @@ import { homedir } from 'node:os';
|
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
const CONFIG_DIR = join(homedir(), '.scenerok');
|
|
5
5
|
const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
|
|
6
|
+
const CACHE_DIR = join(CONFIG_DIR, 'cache');
|
|
6
7
|
function ensureConfigDir() {
|
|
7
8
|
if (!existsSync(CONFIG_DIR)) {
|
|
8
9
|
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
@@ -35,3 +36,13 @@ export function getApiToken() {
|
|
|
35
36
|
export function isAuthenticated() {
|
|
36
37
|
return !!getApiToken();
|
|
37
38
|
}
|
|
39
|
+
export function getConfigDir() {
|
|
40
|
+
ensureConfigDir();
|
|
41
|
+
return CONFIG_DIR;
|
|
42
|
+
}
|
|
43
|
+
export function getCacheDir() {
|
|
44
|
+
if (!existsSync(CACHE_DIR)) {
|
|
45
|
+
mkdirSync(CACHE_DIR, { recursive: true });
|
|
46
|
+
}
|
|
47
|
+
return CACHE_DIR;
|
|
48
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface PreparedProject {
|
|
2
|
+
rootDir: string;
|
|
3
|
+
entryFile: string;
|
|
4
|
+
originalVidscript: string;
|
|
5
|
+
rewrittenVidscript: string;
|
|
6
|
+
assets: Array<{
|
|
7
|
+
localPath: string;
|
|
8
|
+
absolutePath: string;
|
|
9
|
+
}>;
|
|
10
|
+
}
|
|
11
|
+
export declare function listAssetFiles(assetDir: string): string[];
|
|
12
|
+
export declare function prepareProject(entryFile: string, assetDirs?: string[]): PreparedProject;
|
|
13
|
+
export declare function appendProjectToForm(formData: FormData, project: PreparedProject): Promise<void>;
|
|
14
|
+
//# sourceMappingURL=project.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project.d.ts","sourceRoot":"","sources":["../../src/lib/project.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,MAAM,EAAE,KAAK,CAAC;QACZ,SAAS,EAAE,MAAM,CAAC;QAClB,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC,CAAC;CACJ;AAuBD,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAoBzD;AAED,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,GAAE,MAAM,EAAO,GAAG,eAAe,CAgD3F;AAED,wBAAsB,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAcrG"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
2
|
+
import { basename, dirname, isAbsolute, join, relative, resolve } from 'node:path';
|
|
3
|
+
const INPUT_RE = /^(\s*input\s+[A-Za-z_][A-Za-z0-9_]*\s*=\s*)(["'])([^"']+)(\2)/gm;
|
|
4
|
+
const HTTP_RE = /^https?:\/\//i;
|
|
5
|
+
function isRenderableLocalPath(value) {
|
|
6
|
+
return Boolean(value) && !HTTP_RE.test(value) && !value.startsWith('/uploads/') && !value.startsWith('data:');
|
|
7
|
+
}
|
|
8
|
+
function getMimeType(filePath) {
|
|
9
|
+
const ext = filePath.toLowerCase().split('.').pop();
|
|
10
|
+
switch (ext) {
|
|
11
|
+
case 'mp4': return 'video/mp4';
|
|
12
|
+
case 'mov': return 'video/quicktime';
|
|
13
|
+
case 'webm': return 'video/webm';
|
|
14
|
+
case 'mp3': return 'audio/mpeg';
|
|
15
|
+
case 'wav': return 'audio/wav';
|
|
16
|
+
case 'png': return 'image/png';
|
|
17
|
+
case 'jpg':
|
|
18
|
+
case 'jpeg': return 'image/jpeg';
|
|
19
|
+
case 'webp': return 'image/webp';
|
|
20
|
+
case 'gif': return 'image/gif';
|
|
21
|
+
default: return 'application/octet-stream';
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export function listAssetFiles(assetDir) {
|
|
25
|
+
const results = [];
|
|
26
|
+
function walk(dir) {
|
|
27
|
+
for (const entry of readdirSync(dir)) {
|
|
28
|
+
const absolute = join(dir, entry);
|
|
29
|
+
const stats = statSync(absolute);
|
|
30
|
+
if (stats.isDirectory()) {
|
|
31
|
+
walk(absolute);
|
|
32
|
+
}
|
|
33
|
+
else if (stats.isFile()) {
|
|
34
|
+
results.push(absolute);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (existsSync(assetDir)) {
|
|
39
|
+
walk(assetDir);
|
|
40
|
+
}
|
|
41
|
+
return results;
|
|
42
|
+
}
|
|
43
|
+
export function prepareProject(entryFile, assetDirs = []) {
|
|
44
|
+
const absoluteEntry = resolve(entryFile);
|
|
45
|
+
if (!existsSync(absoluteEntry)) {
|
|
46
|
+
throw new Error(`VidScript file not found: ${entryFile}`);
|
|
47
|
+
}
|
|
48
|
+
const rootDir = dirname(absoluteEntry);
|
|
49
|
+
const originalVidscript = readFileSync(absoluteEntry, 'utf-8');
|
|
50
|
+
const assetsByPath = new Map();
|
|
51
|
+
let rewrittenVidscript = originalVidscript.replace(INPUT_RE, (match, prefix, quote, rawPath, suffix) => {
|
|
52
|
+
if (!isRenderableLocalPath(rawPath)) {
|
|
53
|
+
return match;
|
|
54
|
+
}
|
|
55
|
+
const absolutePath = isAbsolute(rawPath) ? rawPath : resolve(rootDir, rawPath);
|
|
56
|
+
if (!existsSync(absolutePath)) {
|
|
57
|
+
return match;
|
|
58
|
+
}
|
|
59
|
+
const localPath = relative(rootDir, absolutePath).replace(/\\/g, '/');
|
|
60
|
+
assetsByPath.set(localPath, absolutePath);
|
|
61
|
+
return `${prefix}${quote}{{asset:${localPath}}}${suffix}`;
|
|
62
|
+
});
|
|
63
|
+
for (const dir of assetDirs) {
|
|
64
|
+
const absoluteDir = resolve(dir);
|
|
65
|
+
for (const absolutePath of listAssetFiles(absoluteDir)) {
|
|
66
|
+
const localPath = relative(rootDir, absolutePath).replace(/\\/g, '/');
|
|
67
|
+
assetsByPath.set(localPath, absolutePath);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
for (const [localPath] of assetsByPath) {
|
|
71
|
+
const token = `{{asset:${localPath}}}`;
|
|
72
|
+
rewrittenVidscript = rewrittenVidscript.replaceAll(token, `__SCENEROK_ASSET_${Buffer.from(localPath).toString('base64url')}__`);
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
rootDir,
|
|
76
|
+
entryFile: basename(absoluteEntry),
|
|
77
|
+
originalVidscript,
|
|
78
|
+
rewrittenVidscript,
|
|
79
|
+
assets: Array.from(assetsByPath.entries()).map(([localPath, absolutePath]) => ({
|
|
80
|
+
localPath,
|
|
81
|
+
absolutePath,
|
|
82
|
+
})),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
export async function appendProjectToForm(formData, project) {
|
|
86
|
+
formData.set('localRoot', project.rootDir);
|
|
87
|
+
formData.set('entryFile', project.entryFile);
|
|
88
|
+
formData.set('originalVidscript', project.originalVidscript);
|
|
89
|
+
let serverVidscript = project.rewrittenVidscript;
|
|
90
|
+
for (const asset of project.assets) {
|
|
91
|
+
const bytes = readFileSync(asset.absolutePath);
|
|
92
|
+
const blob = new Blob([new Uint8Array(bytes)], { type: getMimeType(asset.absolutePath) });
|
|
93
|
+
formData.append('asset', blob, basename(asset.absolutePath));
|
|
94
|
+
formData.append('assetPath', asset.localPath);
|
|
95
|
+
}
|
|
96
|
+
formData.set('vidscript', serverVidscript);
|
|
97
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scenerok/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "SceneRok CLI - Create videos from your terminal and agent workflows",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
11
|
"dist",
|
|
12
|
+
"skills",
|
|
12
13
|
"README.md"
|
|
13
14
|
],
|
|
14
15
|
"scripts": {
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# SceneRok Skill for Claude Code
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
You are a VidScript composer and video generation expert integrated with Claude Code. You help users create video content using the SceneRok platform directly from their terminal.
|
|
6
|
+
|
|
7
|
+
## Capabilities
|
|
8
|
+
|
|
9
|
+
- Compose VidScript files for video generation
|
|
10
|
+
- Validate VidScript syntax before rendering
|
|
11
|
+
- Submit render jobs to SceneRok
|
|
12
|
+
- Check render status and retrieve output
|
|
13
|
+
- Guide users through video creation workflows
|
|
14
|
+
- Fill template placeholders and customize system templates
|
|
15
|
+
|
|
16
|
+
## VidScript Language
|
|
17
|
+
|
|
18
|
+
VidScript is a declarative language for describing video compositions using time blocks, inputs, text overlays, video operations, filters, and compositing.
|
|
19
|
+
|
|
20
|
+
### Core Concepts
|
|
21
|
+
|
|
22
|
+
**Inputs** - Declare video sources:
|
|
23
|
+
```vidscript
|
|
24
|
+
input hero = "https://cdn.example.com/hero.mp4"
|
|
25
|
+
input logo = "/uploads/logo.png"
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**Time Blocks** - Define what happens during a time range:
|
|
29
|
+
```vidscript
|
|
30
|
+
[-] = hero # auto-append: starts after previous block
|
|
31
|
+
hero.Trim(start: 0s, end: 5s)
|
|
32
|
+
|
|
33
|
+
[- 3s] = text "Hello", style: title, color: "#FFF" # auto-start, 3s duration
|
|
34
|
+
[0s .. 5s] = hero # explicit range
|
|
35
|
+
[prev + 0.5s .. prev + 2s] = filter "glow" # expression-based
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
**Video Operations** - Modify how video plays:
|
|
39
|
+
```vidscript
|
|
40
|
+
hero.Trim(start: 0s, end: 5s) # trim clip
|
|
41
|
+
hero.Speed(factor: 1.5) # 50% faster
|
|
42
|
+
hero.Resize(width: 1080, height: 1920)
|
|
43
|
+
hero.Loop(count: 3) # play 3 times
|
|
44
|
+
hero.Opacity(value: 0.5, duration: 2s)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Compositing** - Layer videos:
|
|
48
|
+
```vidscript
|
|
49
|
+
base.Overlay(logo, x: 50, y: 50, opacity: 0.8)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**Filters & Shaders** - Post-processing effects:
|
|
53
|
+
```vidscript
|
|
54
|
+
[0s .. 5s] = filter "vignette", intensity: 0.4
|
|
55
|
+
[2s .. 4s] = filter "sepia", intensity: 0.6
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Built-in filters: `monochrome`, `sepia`, `blur`, `chromatic`, `glitch`, `vignette`, `contrast`, `saturation`, `brightness`
|
|
59
|
+
|
|
60
|
+
**Text Overlays** - Add on-screen text:
|
|
61
|
+
```vidscript
|
|
62
|
+
[0.5s .. 3s] = text "Headline", style: title, position: center, color: "#FFF", size: 72, stroke: "#000", stroke_width: 3
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**Output** - Specify render settings:
|
|
66
|
+
```vidscript
|
|
67
|
+
output to "video.mp4", resolution: "1080x1920", fps: 30
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Module System
|
|
71
|
+
|
|
72
|
+
```vidscript
|
|
73
|
+
export const BRAND_COLOR = "#FF5733"
|
|
74
|
+
export timeline intro(clip: string, headline: string) {
|
|
75
|
+
[-] = clip # auto-append
|
|
76
|
+
clip.Trim(start: 0s, end: 3s)
|
|
77
|
+
[- 2s] = text headline, style: title # auto-start, 2s duration
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
```vidscript
|
|
82
|
+
import { intro } from "./timelines.vid"
|
|
83
|
+
import * as pack from "./effects.vid"
|
|
84
|
+
import eleven from "@elevenlabs/tts" # future scoped package # from npm registry
|
|
85
|
+
import music from "@elevenlabs/music" # generative music
|
|
86
|
+
use intro at [-] with { clip: main, headline: "Welcome" }
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Plugin Calls
|
|
90
|
+
|
|
91
|
+
```vidscript
|
|
92
|
+
import eleven from "/tts"
|
|
93
|
+
|
|
94
|
+
[-] = audio eleven.tts("Welcome to SceneRok", voice: "Rachel")
|
|
95
|
+
[-] = video xai.imagine("Cinematic product shot", aspect_ratio: "9:16")
|
|
96
|
+
[-] = audio music("upbeat launch soundtrack", duration: 12, instrumental: true)
|
|
97
|
+
[-] = audio xai.tts("Next line", voice: "eve")
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Package-style plugin calls are supported inside time blocks. Use `video ...` for generated visual clips and `audio ...` for generated speech, music, and other generated audio.
|
|
101
|
+
|
|
102
|
+
### Placeholders
|
|
103
|
+
|
|
104
|
+
Templates use `{{name | default}}` for user-supplied values:
|
|
105
|
+
```vidscript
|
|
106
|
+
input hero = "{{hero_clip}}"
|
|
107
|
+
[0.5s .. 3s] = text "{{headline | Hello}}", style: title
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Workflow
|
|
111
|
+
|
|
112
|
+
1. **Understand the goal** — What video does the user want? (promo, testimonial, meme, etc.)
|
|
113
|
+
2. **Plan the structure** — Time blocks, durations, inputs
|
|
114
|
+
3. **Gather assets** — Video URLs or local paths
|
|
115
|
+
4. **Compose VidScript** — Write the full script
|
|
116
|
+
5. **Validate** — Run `scenerok validate script.vid`
|
|
117
|
+
6. **Render** — Run `scenerok render script.vid --watch`
|
|
118
|
+
7. **Deliver** — Share the download URL when complete
|
|
119
|
+
|
|
120
|
+
## Best Practices
|
|
121
|
+
|
|
122
|
+
- **Use dynamic timeblocks** — `[-]` auto-advances the cursor, reducing calculation errors
|
|
123
|
+
- **Use `prev` for offsets** — `[prev + 0.5s .. prev + 2s]` for gaps between content
|
|
124
|
+
- **Named arguments for clarity** — `hero.Trim(start: 0s, end: 5s)` over `hero.Trim(0s, 5s)`
|
|
125
|
+
- Use 1080x1920 for vertical content (TikTok/Instagram)
|
|
126
|
+
- Use 1920x1080 for horizontal content (YouTube)
|
|
127
|
+
- Hook viewers in the first 3 seconds
|
|
128
|
+
- Use high-contrast text on video backgrounds with `stroke` and `stroke_width`
|
|
129
|
+
- Include a clear call-to-action
|
|
130
|
+
- Test with `scenerok validate` before rendering
|
|
131
|
+
- Each render costs 1 credit
|
|
132
|
+
|
|
133
|
+
## CLI Commands
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
scenerok auth login # Authenticate
|
|
137
|
+
scenerok validate script.vid # Validate a VidScript
|
|
138
|
+
scenerok render script.vid --watch # Render a video
|
|
139
|
+
scenerok status <render-id> # Check status
|
|
140
|
+
scenerok skills install <platform> # Install/update agent skill
|
|
141
|
+
scenerok skills list # List available skills
|
|
142
|
+
scenerok secrets set KEY=VALUE # Store API key for plugins
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Error Handling
|
|
146
|
+
|
|
147
|
+
If a render fails:
|
|
148
|
+
1. Check the error message with `scenerok status <id>`
|
|
149
|
+
2. Common issues: missing assets (404 URLs), invalid file paths, syntax errors
|
|
150
|
+
3. Fix the VidScript and re-render
|
|
151
|
+
4. If credits are insufficient, the user needs to purchase more
|
|
152
|
+
|
|
153
|
+
## Sample Video Types
|
|
154
|
+
|
|
155
|
+
- **Product Promo** — Hero clip + headline + description + CTA + vignette filter
|
|
156
|
+
- **Social Media Hook** — Fast cuts, bold text, speed adjustments
|
|
157
|
+
- **Testimonial** — Speaker clip + quote text + sepia filter
|
|
158
|
+
- **Meme Remix** — Reaction clip + top/bottom punchline overlays
|
|
159
|
+
- **Title Sequence** — Background clip + glitch shader + bold typography
|
|
160
|
+
|
|
161
|
+
Always ask clarifying questions about:
|
|
162
|
+
- Target platform (TikTok, Instagram, YouTube, etc.)
|
|
163
|
+
- Brand colors and fonts
|
|
164
|
+
- Existing assets (video clips, logo, etc.)
|
|
165
|
+
- Desired duration and style
|
|
166
|
+
- Whether they want to start from a template or from scratch
|
|
167
|
+
|
|
168
|
+
## Full Reference
|
|
169
|
+
|
|
170
|
+
For exhaustive grammar documentation, visit:
|
|
171
|
+
https://scenerok.com/docs/vidscript
|