UNPKG

scrptly

Version:

Scrptly is a Video Development Kit (VDK) for programmatically generating AI-powered videos.

270 lines (269 loc) 10.9 kB
import AssetUploader from './assetUploader.js'; import Renderer from './renderer.js'; import Agent from './agent.js'; import { Listr, SilentRenderer } from 'listr2'; import BaseLayer from './layers/BaseLayer.js'; import FolderLayer from './layers/FolderLayer.js'; import TextLayer from './layers/TextLayer.js'; import CaptionsLayer from './layers/CaptionsLayer.js'; import ImageLayer from './layers/ImageLayer.js'; import VideoLayer from './layers/VideoLayer.js'; import AudioLayer from './layers/AudioLayer.js'; import TTSLayer from './layers/TTSLayer.js'; import ChartLayer from './layers/ChartLayer.js'; export { BaseLayer, FolderLayer, TextLayer, CaptionsLayer, ImageLayer, VideoLayer, AudioLayer, TTSLayer, ChartLayer }; export { default as TextualLayer } from './layers/TextualLayer.js'; export { default as AuditoryLayer } from './layers/AuditoryLayer.js'; export { default as MediaLayer } from './layers/MediaLayer.js'; export { default as VisualLayer } from './layers/VisualLayer.js'; ; ; const scriptlySettings = { apiKey: false, apiEndpoint: 'https://api.scrptly.com/', }; export default class Scrptly { settings; layers = []; flow = []; _flowPointer = this.flow; prepareAssetsTask = null; renderVideoTask = null; generateProjectTask = null; renderCtx = {}; generateCtx = {}; createAiProjectTask = null; generateAiVideoTask = null; constructor(settings = {}) { this.settings = { ...(this.constructor.defaultSettings), ...settings, }; } static get defaultSettings() { return { size: { width: 1920, height: 1080 }, frameRate: 30, backgroundColor: '#00000000', defaults: { easing: 'easeInOut', fontFamily: 'Noto Sans', cacheIntegrations: true, } }; } static setApiSettings(settings) { for (const k of Object.keys(settings)) { scriptlySettings[k] = settings[k]; } } // Flow control pushAction(action) { this._flowPointer.push(action); } wait(time) { this.pushAction({ statement: 'wait', duration: time }); return this; } parallel(funcs, settings) { let initialPointer = this._flowPointer; let actions = []; funcs.forEach(fn => { this._flowPointer = []; actions.push(this._flowPointer); fn(); }); this._flowPointer = initialPointer; this.pushAction({ statement: 'parallel', actions }); } addLayer(LayerClass, properties = {}, settings = {}, options = {}) { const layer = new LayerClass(this, properties, settings); this.layers.push(layer); this.pushAction({ statement: 'addLayer', id: layer.id, type: LayerClass.type, settings, properties, options }); return layer; } /*addFolder(properties?: Record<string, any>, settings?: Record<string, any>, options?: AddLayerOptions) { return this.addLayer(FolderLayer, properties, settings, options); }*/ addText(properties, settings, options) { return this.addLayer(TextLayer, properties, settings, options); } addImage(properties, settings, options) { return this.addLayer(ImageLayer, properties, settings, options); } addVideo(properties, settings, options) { return this.addLayer(VideoLayer, properties, settings, options); } addAudio(properties, settings, options) { return this.addLayer(AudioLayer, properties, settings, options); } addCaptions(properties, settings, options) { settings; return this.addLayer(CaptionsLayer, properties, settings, options); } addTTS(properties, settings, options) { return this.addLayer(TTSLayer, properties, settings, options); } addChart(properties, settings, options) { return this.addLayer(ChartLayer, properties, settings, options); } // API calls async apiCall(endpoint, options = {}) { if (!scriptlySettings.apiKey) throw new Error('API key not set'); const url = `${scriptlySettings.apiEndpoint}${endpoint}`; const response = await fetch(url, { method: options?.method || 'GET', ...options, headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${scriptlySettings.apiKey}`, ...(options?.headers || {}), }, }); if (!response.ok) throw new Error(`API call failed: ${response.statusText}`); return await response.json(); } async info() { const response = await this.apiCall('info'); return response; } async prepareAssets(actions = this.flow) { for (let action of actions) { if (action.statement === 'addLayer') { let layer = this.layers.find(l => l.id === action.id); if (layer && layer.constructor.isAsset && 'source' in layer.settings && layer.settings.sourceType == 'file') { this.prepareAssetsTask.output = `Uploading ${layer.settings.source}...`; let asset = new AssetUploader(this, layer.settings.source, layer.constructor.type); let response = await asset.uploadAsset(); layer.settings.source = response.url; layer.settings.sourceType = 'asset'; action.settings.source = response.url; action.settings.sourceType = 'asset'; // Prepare image assets for video layers if (action.type === 'video' && layer.settings?.image?.source && layer.settings.image.sourceType === 'file') { this.prepareAssetsTask.output = `Uploading ${layer.settings.image.source}...`; let asset = new AssetUploader(this, layer.settings.image.source, layer.constructor.type); let response = await asset.uploadAsset(); layer.settings.image.source = response.url; layer.settings.image.sourceType = 'asset'; action.settings.image.source = response.url; action.settings.image.sourceType = 'asset'; } } } else if (action.statement === 'parallel') { for (let subActions of action.actions) { await this.prepareAssets(subActions); } } } return true; } async renderVideo(options = {}) { options = Object.assign({ verbose: true, }, options); this.renderCtx = {}; const tasks = new Listr([ { title: 'Preparing assets', task: async (ctx, task) => { this.prepareAssetsTask = task; await this.prepareAssets(); } }, { title: 'Rendering video', task: async (ctx, task) => { this.renderVideoTask = task; const renderer = new Renderer(this, options, this.settings, this.flow); ctx.result = await renderer.render(); }, rendererOptions: { persistentOutput: true, }, } ], { renderer: options.verbose === false ? SilentRenderer : 'default', ctx: this.renderCtx }); await tasks.run(); return tasks.ctx.result; } async generateProject(options = {}) { options = Object.assign({ verbose: true, }, options); this.renderCtx = {}; const tasks = new Listr([ { title: 'Preparing assets', task: async (ctx, task) => { this.prepareAssetsTask = task; await this.prepareAssets(); } }, { title: 'Generating project', task: async (ctx, task) => { this.generateProjectTask = task; const renderer = new Renderer(this, options, this.settings, this.flow); ctx.result = await renderer.generateProject(); }, rendererOptions: { persistentOutput: true, }, } ], { renderer: options.verbose === false ? SilentRenderer : 'default', ctx: this.renderCtx }); await tasks.run(); return tasks.ctx.result; } async generateAiVideo(parameters, options) { options = Object.assign({ verbose: true, }, options); if (options.verbose && !parameters.approveUpTo) console.warn('⚠️ The "approveUpTo" option is highly recommended. This option specifies the maximum number of tokens you are willing to approve for the AI-generated video. If the cost to generate the video exceeds this amount, the generation will be processed partially; note that your account will still be billed for the usage incurred. When unspecified, it defaults to 10,000 tokens.'); this.generateCtx = {}; const tasks = new Listr([ { title: 'Uploading context images', task: async (ctx, task) => { this.prepareAssetsTask = task; for (let item of (parameters.context || [])) { if (!item.url.startsWith('https://') && !item.url.startsWith('http://')) { // Upload local file this.prepareAssetsTask.output = `Uploading "${item.url}"...`; let asset = new AssetUploader(this, item.url, 'image'); let response = await asset.uploadAsset(); item.url = response.url; } } } }, { title: 'Generating AI Video', task: async (ctx, task) => { this.generateAiVideoTask = task; const agent = new Agent(this, parameters, options); return await agent.generateAiVideo(ctx); }, rendererOptions: { persistentOutput: true, }, } ], { renderer: options.verbose === false ? SilentRenderer : 'default', ctx: this.generateCtx, rendererOptions: { collapseSubtasks: false } }); await tasks.run(); return tasks.ctx.result; } }