@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,88 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import { pathToFileURL } from 'node:url';
|
|
3
|
+
import { assertRequiredApiEnv, env } from '../config/env.js';
|
|
4
|
+
import { createJobsRouter } from './routes/jobs.js';
|
|
5
|
+
import { createProjectAssetsRouter } from './routes/projectAssets.js';
|
|
6
|
+
import { createProjectsRouter } from './routes/projects.js';
|
|
7
|
+
import { createWebhookRouter } from './routes/webhooks.js';
|
|
8
|
+
import { requireBearerToken, requireBearerTokenOrAccessToken } from './middleware/auth.js';
|
|
9
|
+
import { buildOpenApiSpec } from './openapi/spec.js';
|
|
10
|
+
|
|
11
|
+
const swaggerUiHtml = `<!doctype html>
|
|
12
|
+
<html lang="en">
|
|
13
|
+
<head>
|
|
14
|
+
<meta charset="utf-8" />
|
|
15
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
16
|
+
<title>Rilo API Docs</title>
|
|
17
|
+
<link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css" />
|
|
18
|
+
</head>
|
|
19
|
+
<body>
|
|
20
|
+
<div id="swagger-ui"></div>
|
|
21
|
+
<script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js"></script>
|
|
22
|
+
<script>
|
|
23
|
+
window.ui = SwaggerUIBundle({
|
|
24
|
+
url: '/openapi.json',
|
|
25
|
+
dom_id: '#swagger-ui'
|
|
26
|
+
});
|
|
27
|
+
</script>
|
|
28
|
+
</body>
|
|
29
|
+
</html>`;
|
|
30
|
+
|
|
31
|
+
function getRequestBaseUrl(req, { fallbackPort = 3000 } = {}) {
|
|
32
|
+
const forwardedProtoHeader = req.get('x-forwarded-proto');
|
|
33
|
+
const hostHeader = req.get('x-forwarded-host') || req.get('host');
|
|
34
|
+
const protocol = forwardedProtoHeader
|
|
35
|
+
? forwardedProtoHeader.split(',')[0].trim()
|
|
36
|
+
: req.protocol || 'http';
|
|
37
|
+
|
|
38
|
+
if (hostHeader) {
|
|
39
|
+
return `${protocol}://${hostHeader}`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return `http://localhost:${fallbackPort}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function createApiApp({ baseUrl } = {}) {
|
|
46
|
+
assertRequiredApiEnv();
|
|
47
|
+
|
|
48
|
+
const app = express();
|
|
49
|
+
app.set('trust proxy', true);
|
|
50
|
+
app.use(express.json({ limit: '2mb' }));
|
|
51
|
+
|
|
52
|
+
app.get('/health', (_req, res) => {
|
|
53
|
+
res.json({ ok: true, service: 'rilo' });
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
app.get('/openapi.json', (req, res) => {
|
|
57
|
+
const resolvedBaseUrl =
|
|
58
|
+
typeof baseUrl === 'string' && baseUrl.trim().length > 0
|
|
59
|
+
? baseUrl
|
|
60
|
+
: getRequestBaseUrl(req, { fallbackPort: env.port });
|
|
61
|
+
const openApiSpec = buildOpenApiSpec({ baseUrl: resolvedBaseUrl });
|
|
62
|
+
res.json(openApiSpec);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
app.get('/docs', (_req, res) => {
|
|
66
|
+
res.type('html').send(swaggerUiHtml);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
app.use('/webhooks', createWebhookRouter());
|
|
70
|
+
app.use('/projects', requireBearerTokenOrAccessToken, createProjectAssetsRouter());
|
|
71
|
+
app.use(requireBearerToken);
|
|
72
|
+
app.use('/jobs', createJobsRouter());
|
|
73
|
+
app.use('/projects', createProjectsRouter());
|
|
74
|
+
|
|
75
|
+
return app;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function startApiServer({ port = env.port, baseUrl } = {}) {
|
|
79
|
+
const app = createApiApp({ baseUrl });
|
|
80
|
+
return app.listen(port, () => {
|
|
81
|
+
console.log(`rilo api listening on :${port}`);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const entryPoint = process.argv[1] ? pathToFileURL(process.argv[1]).href : '';
|
|
86
|
+
if (import.meta.url === entryPoint) {
|
|
87
|
+
startApiServer();
|
|
88
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { env } from '../config/env.js';
|
|
2
|
+
|
|
3
|
+
let initialized = false;
|
|
4
|
+
let admin;
|
|
5
|
+
let db;
|
|
6
|
+
let bucket;
|
|
7
|
+
|
|
8
|
+
export async function getFirebaseClients(options = {}) {
|
|
9
|
+
const envOverride = options.env || env;
|
|
10
|
+
const importFirebaseAdmin = options.importFirebaseAdmin || (async () => import('firebase-admin'));
|
|
11
|
+
|
|
12
|
+
if (initialized) {
|
|
13
|
+
return { admin, db, bucket };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const adminModule = await importFirebaseAdmin();
|
|
17
|
+
admin = adminModule.default;
|
|
18
|
+
|
|
19
|
+
const hasExplicitCredentials =
|
|
20
|
+
envOverride.firebaseProjectId && envOverride.firebaseClientEmail && envOverride.firebasePrivateKey;
|
|
21
|
+
|
|
22
|
+
const appConfig = hasExplicitCredentials
|
|
23
|
+
? {
|
|
24
|
+
credential: admin.credential.cert({
|
|
25
|
+
projectId: envOverride.firebaseProjectId,
|
|
26
|
+
clientEmail: envOverride.firebaseClientEmail,
|
|
27
|
+
privateKey: envOverride.firebasePrivateKey.replace(/\\n/g, '\n')
|
|
28
|
+
}),
|
|
29
|
+
storageBucket: envOverride.firebaseStorageBucket || undefined
|
|
30
|
+
}
|
|
31
|
+
: {
|
|
32
|
+
credential: admin.credential.applicationDefault(),
|
|
33
|
+
storageBucket: envOverride.firebaseStorageBucket || undefined
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
admin.initializeApp(appConfig);
|
|
37
|
+
db = admin.firestore();
|
|
38
|
+
bucket = admin.storage().bucket();
|
|
39
|
+
initialized = true;
|
|
40
|
+
|
|
41
|
+
return { admin, db, bucket };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function __resetFirebaseClientsForTests() {
|
|
45
|
+
initialized = false;
|
|
46
|
+
admin = undefined;
|
|
47
|
+
db = undefined;
|
|
48
|
+
bucket = undefined;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function buildStorageHttpUrl(bucketName, objectPath) {
|
|
52
|
+
const encodedPath = objectPath
|
|
53
|
+
.split('/')
|
|
54
|
+
.map((segment) => encodeURIComponent(segment))
|
|
55
|
+
.join('/');
|
|
56
|
+
return `https://storage.googleapis.com/${bucketName}/${encodedPath}`;
|
|
57
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { env } from '../config/env.js';
|
|
4
|
+
import { logError, logInfo } from '../observability/logger.js';
|
|
5
|
+
import { buildStorageHttpUrl, getFirebaseClients } from './firebaseClient.js';
|
|
6
|
+
import { writeProjectSync } from '../store/projectStore.js';
|
|
7
|
+
|
|
8
|
+
async function pathExists(targetPath) {
|
|
9
|
+
try {
|
|
10
|
+
await fs.access(targetPath);
|
|
11
|
+
return true;
|
|
12
|
+
} catch {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function listProjectFiles(projectDir) {
|
|
18
|
+
const files = [];
|
|
19
|
+
|
|
20
|
+
async function walk(currentDir) {
|
|
21
|
+
const entries = await fs.readdir(currentDir, { withFileTypes: true });
|
|
22
|
+
for (const entry of entries) {
|
|
23
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
24
|
+
if (entry.isDirectory()) {
|
|
25
|
+
await walk(fullPath);
|
|
26
|
+
} else {
|
|
27
|
+
files.push(fullPath);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (await pathExists(projectDir)) {
|
|
33
|
+
await walk(projectDir);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return files;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export class LocalOutputBackend {
|
|
40
|
+
async syncProjectSnapshot({ project }) {
|
|
41
|
+
await writeProjectSync(project, {
|
|
42
|
+
backend: 'local',
|
|
43
|
+
syncedAt: new Date().toISOString()
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export class FirebaseOutputBackend {
|
|
49
|
+
constructor(options = {}) {
|
|
50
|
+
this.db = null;
|
|
51
|
+
this.bucket = null;
|
|
52
|
+
this.getFirebaseClientsFn = options.getFirebaseClientsFn || getFirebaseClients;
|
|
53
|
+
this.buildStorageHttpUrlFn = options.buildStorageHttpUrlFn || buildStorageHttpUrl;
|
|
54
|
+
this.writeProjectSyncFn = options.writeProjectSyncFn || writeProjectSync;
|
|
55
|
+
this.logInfoFn = options.logInfoFn || logInfo;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async ensureInitialized() {
|
|
59
|
+
if (this.db && this.bucket) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const clients = await this.getFirebaseClientsFn();
|
|
64
|
+
this.db = clients.db;
|
|
65
|
+
this.bucket = clients.bucket;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async syncProjectSnapshot({ project, projectDir }) {
|
|
69
|
+
await this.ensureInitialized();
|
|
70
|
+
|
|
71
|
+
const files = await listProjectFiles(projectDir);
|
|
72
|
+
const assetRefs = [];
|
|
73
|
+
for (const filePath of files) {
|
|
74
|
+
const relativePath = path.relative(projectDir, filePath).split(path.sep).join('/');
|
|
75
|
+
const destination = `projects/${project}/${relativePath}`;
|
|
76
|
+
await this.bucket.upload(filePath, { destination });
|
|
77
|
+
assetRefs.push({
|
|
78
|
+
relativePath,
|
|
79
|
+
storagePath: destination,
|
|
80
|
+
url: this.buildStorageHttpUrlFn(this.bucket.name, destination)
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const runStatePath = path.join(projectDir, 'run-state.json');
|
|
85
|
+
const artifactsPath = path.join(projectDir, 'artifacts.json');
|
|
86
|
+
const configPath = path.join(projectDir, 'config.json');
|
|
87
|
+
const storyPath = path.join(projectDir, 'story.md');
|
|
88
|
+
|
|
89
|
+
const projectRef = this.db.collection('projects').doc(project);
|
|
90
|
+
await projectRef.set(
|
|
91
|
+
{
|
|
92
|
+
project,
|
|
93
|
+
backend: 'firebase',
|
|
94
|
+
syncedAt: new Date().toISOString()
|
|
95
|
+
},
|
|
96
|
+
{ merge: true }
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
if (await pathExists(runStatePath)) {
|
|
100
|
+
const runState = JSON.parse(await fs.readFile(runStatePath, 'utf8'));
|
|
101
|
+
await projectRef.collection('documents').doc('run-state').set(runState, { merge: true });
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (await pathExists(artifactsPath)) {
|
|
105
|
+
const artifacts = JSON.parse(await fs.readFile(artifactsPath, 'utf8'));
|
|
106
|
+
await projectRef.collection('documents').doc('artifacts').set(artifacts, { merge: true });
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (await pathExists(configPath)) {
|
|
110
|
+
const config = JSON.parse(await fs.readFile(configPath, 'utf8'));
|
|
111
|
+
await projectRef.collection('documents').doc('config').set(config, { merge: true });
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (await pathExists(storyPath)) {
|
|
115
|
+
const story = await fs.readFile(storyPath, 'utf8');
|
|
116
|
+
await projectRef.collection('documents').doc('story').set({ markdown: story }, { merge: true });
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
await projectRef.collection('documents').doc('assets-manifest').set(
|
|
120
|
+
{
|
|
121
|
+
refs: assetRefs,
|
|
122
|
+
syncedAt: new Date().toISOString()
|
|
123
|
+
},
|
|
124
|
+
{ merge: true }
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
await projectRef.collection('documents').doc('sync').set(
|
|
128
|
+
{
|
|
129
|
+
backend: 'firebase',
|
|
130
|
+
syncedAt: new Date().toISOString(),
|
|
131
|
+
uploadedFiles: files.length
|
|
132
|
+
},
|
|
133
|
+
{ merge: true }
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
await this.writeProjectSyncFn(project, {
|
|
137
|
+
backend: 'firebase',
|
|
138
|
+
syncedAt: new Date().toISOString(),
|
|
139
|
+
uploadedFiles: files.length
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
this.logInfoFn('firebase_sync_completed', { project, uploadedFiles: files.length });
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
let backend;
|
|
147
|
+
|
|
148
|
+
export function getOutputBackend(options = {}) {
|
|
149
|
+
const backendType = options.backendType || env.outputBackend;
|
|
150
|
+
|
|
151
|
+
if (backend) {
|
|
152
|
+
return backend;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (backendType === 'firebase') {
|
|
156
|
+
backend = new FirebaseOutputBackend({
|
|
157
|
+
getFirebaseClientsFn: options.getFirebaseClientsFn,
|
|
158
|
+
buildStorageHttpUrlFn: options.buildStorageHttpUrlFn,
|
|
159
|
+
writeProjectSyncFn: options.writeProjectSyncFn,
|
|
160
|
+
logInfoFn: options.logInfoFn
|
|
161
|
+
});
|
|
162
|
+
} else {
|
|
163
|
+
backend = new LocalOutputBackend();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return backend;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function __resetOutputBackendForTests() {
|
|
170
|
+
backend = undefined;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export async function syncProjectSnapshot(payload, options = {}) {
|
|
174
|
+
const outputBackend = options.outputBackend || getOutputBackend(options);
|
|
175
|
+
const logErrorFn = options.logErrorFn || logError;
|
|
176
|
+
try {
|
|
177
|
+
await outputBackend.syncProjectSnapshot(payload);
|
|
178
|
+
} catch (error) {
|
|
179
|
+
logErrorFn('output_backend_sync_failed', {
|
|
180
|
+
backend: options.backendType || env.outputBackend,
|
|
181
|
+
project: payload.project,
|
|
182
|
+
error: error.message
|
|
183
|
+
});
|
|
184
|
+
throw error;
|
|
185
|
+
}
|
|
186
|
+
}
|