@telepat/rilo 0.1.0
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 +21 -0
- package/README.md +209 -0
- package/index.js +1 -0
- package/models/black-forest-labs__flux-2-pro.json +78 -0
- package/models/black-forest-labs__flux-schnell.json +95 -0
- package/models/bytedance__seedream-4.json +71 -0
- package/models/deepseek-ai__deepseek-v3.json +61 -0
- package/models/google__nano-banana-pro.json +92 -0
- package/models/google__veo-3.1-fast.json +93 -0
- package/models/google__veo-3.1.json +93 -0
- package/models/jaaari__kokoro-82m.json +86 -0
- package/models/kwaivgi__kling-v3-video.json +101 -0
- package/models/minimax__speech-02-turbo.json +141 -0
- package/models/pixverse__pixverse-v5.6.json +113 -0
- package/models/prunaai__z-image-turbo.json +107 -0
- package/models/resemble-ai__chatterbox-turbo.json +102 -0
- package/models/wan-video__wan-2.2-i2v-fast.json +139 -0
- package/package.json +67 -0
- package/src/api/firebaseFunction.js +46 -0
- package/src/api/middleware/auth.js +70 -0
- package/src/api/openapi/generateOpenApi.js +21 -0
- package/src/api/openapi/spec.js +831 -0
- package/src/api/routes/jobs.js +45 -0
- package/src/api/routes/projectAssets.js +63 -0
- package/src/api/routes/projects.js +647 -0
- package/src/api/routes/webhooks.js +13 -0
- package/src/api/server.js +88 -0
- package/src/backends/firebaseClient.js +57 -0
- package/src/backends/outputBackend.js +186 -0
- package/src/backends/projectMetadataBackend.js +550 -0
- package/src/cli/commands/openHome.js +70 -0
- package/src/cli/commands/settingsFlow.js +196 -0
- package/src/cli/index.js +192 -0
- package/src/config/env.js +158 -0
- package/src/config/keystore.js +175 -0
- package/src/config/models.js +281 -0
- package/src/config/settingsSchema.js +214 -0
- package/src/media/ffmpeg.js +144 -0
- package/src/media/files.js +77 -0
- package/src/media/subtitles.js +444 -0
- package/src/observability/apiTrace.js +17 -0
- package/src/observability/logger.js +7 -0
- package/src/observability/metrics.js +10 -0
- package/src/pipeline/inputSanitizer.js +6 -0
- package/src/pipeline/orchestrator.js +1669 -0
- package/src/policy/contentGuardrails.js +30 -0
- package/src/providers/predictions.js +188 -0
- package/src/providers/replicateClient.js +12 -0
- package/src/steps/alignSubtitles.js +156 -0
- package/src/steps/burnInSubtitles.js +22 -0
- package/src/steps/composeFinalVideo.js +57 -0
- package/src/steps/generateKeyframes.js +70 -0
- package/src/steps/generateVideoSegments.js +95 -0
- package/src/steps/generateVoiceover.js +128 -0
- package/src/steps/imageToVideoAdapters.js +100 -0
- package/src/steps/script.js +177 -0
- package/src/steps/textToImageAdapters.js +87 -0
- package/src/store/assetStore.js +5 -0
- package/src/store/jobStore.js +102 -0
- package/src/store/projectAnalyticsStore.js +625 -0
- package/src/store/projectStore.js +684 -0
- package/src/store/settingsStore.js +155 -0
- package/src/store/staleAssetStore.js +63 -0
- package/src/types/job.js +28 -0
- package/src/types/media.js +28 -0
- package/src/worker/processor.js +24 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { PUBLIC_SETTINGS, SECURE_SETTINGS } from '../config/settingsSchema.js';
|
|
5
|
+
import { getSecret, setSecret } from '../config/keystore.js';
|
|
6
|
+
|
|
7
|
+
const CONFIG_FILE = 'config.json';
|
|
8
|
+
|
|
9
|
+
function getRiloDir() {
|
|
10
|
+
return path.join(os.homedir(), '.rilo');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function getConfigFilePath() {
|
|
14
|
+
return path.join(getRiloDir(), CONFIG_FILE);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Read the public config file (~/.rilo/config.json).
|
|
19
|
+
* Returns an empty object if the file does not exist.
|
|
20
|
+
* @returns {Promise<Record<string, unknown>>}
|
|
21
|
+
*/
|
|
22
|
+
export async function readPublicConfig() {
|
|
23
|
+
const filePath = getConfigFilePath();
|
|
24
|
+
try {
|
|
25
|
+
const raw = await fs.readFile(filePath, 'utf8');
|
|
26
|
+
const parsed = JSON.parse(raw);
|
|
27
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
28
|
+
return parsed;
|
|
29
|
+
}
|
|
30
|
+
} catch (err) {
|
|
31
|
+
if (err.code !== 'ENOENT') {
|
|
32
|
+
// File exists but is malformed; surface the error
|
|
33
|
+
throw err;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return {};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Write the public config file (~/.rilo/config.json).
|
|
41
|
+
* @param {Record<string, unknown>} data
|
|
42
|
+
*/
|
|
43
|
+
async function writePublicConfig(data) {
|
|
44
|
+
const filePath = getConfigFilePath();
|
|
45
|
+
const dir = getRiloDir();
|
|
46
|
+
await fs.mkdir(dir, { recursive: true });
|
|
47
|
+
await fs.writeFile(filePath, JSON.stringify(data, null, 2), { encoding: 'utf8' });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Resolve the current effective value for a public setting.
|
|
52
|
+
* Precedence: env var > config.json > schema default.
|
|
53
|
+
*
|
|
54
|
+
* @param {import('../config/settingsSchema.js').SETTINGS[0]} setting
|
|
55
|
+
* @param {Record<string, unknown>} storedConfig
|
|
56
|
+
* @returns {{ value: unknown, source: 'env'|'config'|'default' }}
|
|
57
|
+
*/
|
|
58
|
+
export function resolvePublicValue(setting, storedConfig) {
|
|
59
|
+
// Check env vars in priority order
|
|
60
|
+
for (const envName of setting.envNames) {
|
|
61
|
+
const raw = process.env[envName];
|
|
62
|
+
if (raw !== undefined && raw !== '') {
|
|
63
|
+
return { value: raw, source: 'env' };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// Check stored config
|
|
67
|
+
if (Object.prototype.hasOwnProperty.call(storedConfig, setting.configKey)) {
|
|
68
|
+
return { value: storedConfig[setting.configKey], source: 'config' };
|
|
69
|
+
}
|
|
70
|
+
return { value: setting.default, source: 'default' };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Resolve the current effective status for a secure setting.
|
|
75
|
+
* Returns whether a value is present and where it came from.
|
|
76
|
+
*
|
|
77
|
+
* @param {import('../config/settingsSchema.js').SETTINGS[0]} setting
|
|
78
|
+
* @returns {Promise<{ hasValue: boolean, source: 'env'|'keystore'|'none' }>}
|
|
79
|
+
*/
|
|
80
|
+
export async function resolveSecureStatus(setting) {
|
|
81
|
+
for (const envName of setting.envNames) {
|
|
82
|
+
const raw = process.env[envName];
|
|
83
|
+
if (raw !== undefined && raw !== '') {
|
|
84
|
+
return { hasValue: true, source: 'env' };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const stored = await getSecret(setting.keystoreKey);
|
|
88
|
+
if (stored) return { hasValue: true, source: 'keystore' };
|
|
89
|
+
return { hasValue: false, source: 'none' };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Save a single public setting to ~/.rilo/config.json.
|
|
94
|
+
* Merges with existing contents.
|
|
95
|
+
*
|
|
96
|
+
* @param {string} configKey - camelCase key from the schema
|
|
97
|
+
* @param {unknown} value
|
|
98
|
+
*/
|
|
99
|
+
export async function savePublicSetting(configKey, value) {
|
|
100
|
+
const current = await readPublicConfig();
|
|
101
|
+
current[configKey] = value;
|
|
102
|
+
await writePublicConfig(current);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Save a secure token to the OS keystore (or encrypted fallback).
|
|
107
|
+
*
|
|
108
|
+
* @param {string} keystoreKey
|
|
109
|
+
* @param {string} value
|
|
110
|
+
*/
|
|
111
|
+
export async function saveSecureToken(keystoreKey, value) {
|
|
112
|
+
await setSecret(keystoreKey, value);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Load a secure token from the keystore.
|
|
117
|
+
* Respects env-var priority just like the rest of the system.
|
|
118
|
+
*
|
|
119
|
+
* @param {import('../config/settingsSchema.js').SETTINGS[0]} setting
|
|
120
|
+
* @returns {Promise<string|null>}
|
|
121
|
+
*/
|
|
122
|
+
export async function loadSecureToken(setting) {
|
|
123
|
+
for (const envName of setting.envNames) {
|
|
124
|
+
const raw = process.env[envName];
|
|
125
|
+
if (raw !== undefined && raw !== '') return raw;
|
|
126
|
+
}
|
|
127
|
+
return getSecret(setting.keystoreKey);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Build a full snapshot of current settings (for display or env-merging).
|
|
132
|
+
* Secure tokens are returned as their actual value (caller must handle masking).
|
|
133
|
+
*
|
|
134
|
+
* @returns {Promise<{ public: Record<string, { value: unknown, source: string }>, secure: Record<string, { value: string|null, source: string }> }>}
|
|
135
|
+
*/
|
|
136
|
+
export async function loadAllSettings() {
|
|
137
|
+
const storedConfig = await readPublicConfig();
|
|
138
|
+
const publicResult = {};
|
|
139
|
+
for (const s of PUBLIC_SETTINGS) {
|
|
140
|
+
publicResult[s.id] = resolvePublicValue(s, storedConfig);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const secureResult = {};
|
|
144
|
+
for (const s of SECURE_SETTINGS) {
|
|
145
|
+
const status = await resolveSecureStatus(s);
|
|
146
|
+
const value = status.hasValue
|
|
147
|
+
? (status.source === 'env'
|
|
148
|
+
? (process.env[s.envNames[0]] || process.env[s.envNames[1]] || '')
|
|
149
|
+
: await getSecret(s.keystoreKey))
|
|
150
|
+
: null;
|
|
151
|
+
secureResult[s.id] = { value, source: status.source };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return { public: publicResult, secure: secureResult };
|
|
155
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import crypto from 'node:crypto';
|
|
4
|
+
import { ensureDir } from '../media/files.js';
|
|
5
|
+
|
|
6
|
+
async function pathExists(targetPath) {
|
|
7
|
+
try {
|
|
8
|
+
await fs.access(targetPath);
|
|
9
|
+
return true;
|
|
10
|
+
} catch {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function createSnapshotId() {
|
|
16
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
17
|
+
const suffix = crypto.randomBytes(3).toString('hex');
|
|
18
|
+
return `${stamp}-${suffix}`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function listDirectories(rootPath) {
|
|
22
|
+
if (!(await pathExists(rootPath))) {
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
25
|
+
const entries = await fs.readdir(rootPath, { withFileTypes: true });
|
|
26
|
+
return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function listProjectSnapshots(projectDir) {
|
|
30
|
+
const snapshotsRoot = path.join(projectDir, 'snapshots');
|
|
31
|
+
const legacyStaleRoot = path.join(projectDir, 'stale');
|
|
32
|
+
|
|
33
|
+
const snapshots = await listDirectories(snapshotsRoot);
|
|
34
|
+
const legacy = await listDirectories(legacyStaleRoot);
|
|
35
|
+
|
|
36
|
+
return [...snapshots.map((name) => ({ root: 'snapshots', name })), ...legacy.map((name) => ({ root: 'stale', name }))]
|
|
37
|
+
.sort((a, b) => b.name.localeCompare(a.name));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function archiveProjectAssets(projectDir) {
|
|
41
|
+
const assetsPath = path.join(projectDir, 'assets');
|
|
42
|
+
const finalVideoPath = path.join(projectDir, 'final.mp4');
|
|
43
|
+
|
|
44
|
+
const hasAssets = await pathExists(assetsPath);
|
|
45
|
+
const hasFinalVideo = await pathExists(finalVideoPath);
|
|
46
|
+
if (!hasAssets && !hasFinalVideo) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const snapshotsRoot = path.join(projectDir, 'snapshots');
|
|
51
|
+
const snapshotDir = path.join(snapshotsRoot, createSnapshotId());
|
|
52
|
+
await ensureDir(snapshotDir);
|
|
53
|
+
|
|
54
|
+
if (hasAssets) {
|
|
55
|
+
await fs.rename(assetsPath, path.join(snapshotDir, 'assets'));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (hasFinalVideo) {
|
|
59
|
+
await fs.rename(finalVideoPath, path.join(snapshotDir, 'final.mp4'));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return snapshotDir;
|
|
63
|
+
}
|
package/src/types/job.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export const JobStatus = {
|
|
2
|
+
PENDING: 'pending',
|
|
3
|
+
RUNNING: 'running',
|
|
4
|
+
FAILED: 'failed',
|
|
5
|
+
COMPLETED: 'completed'
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const JobStep = {
|
|
9
|
+
SCRIPT: 'script',
|
|
10
|
+
VOICE: 'voiceover',
|
|
11
|
+
KEYFRAMES: 'keyframes',
|
|
12
|
+
SEGMENTS: 'segments',
|
|
13
|
+
COMPOSE: 'compose',
|
|
14
|
+
ALIGN: 'align',
|
|
15
|
+
BURNIN: 'burnin'
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export function emptyStepState() {
|
|
19
|
+
return {
|
|
20
|
+
[JobStep.SCRIPT]: false,
|
|
21
|
+
[JobStep.VOICE]: false,
|
|
22
|
+
[JobStep.KEYFRAMES]: false,
|
|
23
|
+
[JobStep.SEGMENTS]: false,
|
|
24
|
+
[JobStep.COMPOSE]: false,
|
|
25
|
+
[JobStep.ALIGN]: false,
|
|
26
|
+
[JobStep.BURNIN]: false
|
|
27
|
+
};
|
|
28
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export function emptyPipelineArtifacts() {
|
|
2
|
+
return {
|
|
3
|
+
renderSpecVersion: 2,
|
|
4
|
+
aspectRatio: '9:16',
|
|
5
|
+
keyframeSizeKey: '',
|
|
6
|
+
storyHash: '',
|
|
7
|
+
scriptSourceStoryHash: '',
|
|
8
|
+
scriptHash: '',
|
|
9
|
+
shotHashes: [],
|
|
10
|
+
script: '',
|
|
11
|
+
tone: '',
|
|
12
|
+
shots: [],
|
|
13
|
+
timeline: [],
|
|
14
|
+
voiceoverUrl: '',
|
|
15
|
+
voiceoverPath: '',
|
|
16
|
+
keyframeUrls: [],
|
|
17
|
+
keyframePaths: [],
|
|
18
|
+
segmentUrls: [],
|
|
19
|
+
segmentPaths: [],
|
|
20
|
+
subtitleSeedPath: '',
|
|
21
|
+
subtitleAlignedSrtPath: '',
|
|
22
|
+
subtitleAssPath: '',
|
|
23
|
+
finalBaseVideoPath: '',
|
|
24
|
+
finalCaptionedVideoPath: '',
|
|
25
|
+
finalVideoPath: '',
|
|
26
|
+
subtitlesUrl: ''
|
|
27
|
+
};
|
|
28
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { listPendingJobs } from '../store/jobStore.js';
|
|
2
|
+
import { runPipeline } from '../pipeline/orchestrator.js';
|
|
3
|
+
import { logInfo } from '../observability/logger.js';
|
|
4
|
+
|
|
5
|
+
async function tick() {
|
|
6
|
+
const pending = listPendingJobs();
|
|
7
|
+
if (pending.length === 0) return;
|
|
8
|
+
|
|
9
|
+
const [job] = pending;
|
|
10
|
+
logInfo('worker_picking_job', { jobId: job.id });
|
|
11
|
+
await runPipeline(job.id);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async function loop() {
|
|
15
|
+
for (;;) {
|
|
16
|
+
await tick();
|
|
17
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
loop().catch((error) => {
|
|
22
|
+
console.error(error.message);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
});
|