Spaces:
Running
Running
| import { renderFrames, renderStill, stitchFramesToVideo } from '@remotion/renderer'; | |
| import { bundle } from '@remotion/bundler'; | |
| import path from 'path'; | |
| import fs from 'fs'; | |
| import { execSync } from 'child_process'; | |
| const { RenderUtils } = await import('./src/RenderUtils.cjs'); | |
| const { GenerateScript } = await import('./src/GenerateScript.cjs'); | |
| //npx tsc src/GenerateScript.ts --module commonjs --outDir build --esModuleInterop --declaration false | |
| const entry = path.join(process.cwd(), 'src/index.ts'); // your Remotion entry file | |
| const originalManuScriptPath = path.join(process.cwd(), 'public/original_manuscript.json'); | |
| const compositionId = 'IGReelComposition'; | |
| const frameOutputDir = path.join(process.cwd(), 'frames'); | |
| const outputDir = path.join(process.cwd(), 'out'); | |
| const bundleLocation = path.join(process.cwd(), 'build'); | |
| const assetsInfoFile = path.join(process.cwd(), 'public/assetsInfo.json'); | |
| function readAssetInfo() { | |
| if (!fs.existsSync(assetsInfoFile)) { | |
| return undefined | |
| } | |
| let assetsInfoStr = fs.readFileSync(assetsInfoFile) | |
| let assetsInfo = JSON.parse(assetsInfoStr); | |
| return assetsInfo | |
| } | |
| function updateAssetInfo(assetInfoNew) { | |
| fs.writeFileSync(assetsInfoFile, JSON.stringify(assetInfoNew)) | |
| } | |
| export const renderSSR = async (outFile, startFrame, endFrame, controller) => { | |
| const ScriptStr = fs.readFileSync(originalManuScriptPath); | |
| const ScriptInput = JSON.parse(ScriptStr); | |
| let { | |
| duration, | |
| Script, | |
| contents, | |
| intro, | |
| outro, | |
| playListsIDs | |
| } = GenerateScript(ScriptInput) | |
| console.log('Running "npm run bundle"...'); | |
| execSync('npm run bundle', { stdio: 'inherit' }); | |
| console.log('"npm run bundle" completed.'); | |
| console.log('Running "npm run skip-font-warn"...'); | |
| execSync('npm run skip-font-warn', { stdio: 'inherit' }); | |
| console.log('"npm run skip-font-warn" completed.'); | |
| let renderConfig = { | |
| composition: { | |
| id: compositionId, | |
| fps: Script.meta.fps, | |
| height: Script.meta?.resolution?.height, | |
| width: Script.meta?.resolution?.width, | |
| durationInFrames: duration, | |
| defaultProps: Object.assign(Script, { | |
| bgMusic: RenderUtils.getFileName(Script.bgMusic), | |
| contents: contents, | |
| intro: intro, | |
| outro: outro | |
| }), | |
| props: Object.assign(Script, { | |
| bgMusic: RenderUtils.getFileName(Script.bgMusic), | |
| contents: contents, | |
| intro: intro, | |
| outro: outro | |
| }), | |
| }, | |
| serveUrl: bundleLocation, | |
| output: path.join(frameOutputDir, 'output.jpg'), | |
| // additional renderer options | |
| audioCodec: 'mp3', | |
| imageFormat: 'jpeg', | |
| enableMultiProcessOnLinux: true, | |
| jpegQuality: 70, | |
| timeoutInMilliseconds: 60000, | |
| concurrency: 1, | |
| gl: 'angle' | |
| } | |
| const fps = Script.meta?.fps; | |
| if (!fs.existsSync(frameOutputDir)) { | |
| fs.mkdirSync(frameOutputDir, { recursive: true }); | |
| } | |
| const frameNamePattern = 'frame-[frame].[ext]'; | |
| const formatMsToETA = (ms) => { | |
| const totalSeconds = Math.ceil(ms / 1000); | |
| const hours = Math.floor(totalSeconds / 3600); | |
| const minutes = Math.floor((totalSeconds % 3600) / 60); | |
| const seconds = totalSeconds % 60; | |
| const parts = []; | |
| if (hours) parts.push(`${hours}h`); | |
| if (minutes) parts.push(`${minutes}min`); | |
| if (seconds || parts.length === 0) parts.push(`${seconds}sec`); | |
| return parts.join(' '); | |
| }; | |
| let result = await renderFrames({ | |
| ...renderConfig, | |
| onBrowserLog: (info) => { | |
| console.log(`${info.type}: ${info.text}`); | |
| console.log( | |
| info.stackTrace | |
| .map((stack) => { | |
| return ` ${stack.url}:${stack.lineNumber}:${stack.columnNumber}`; | |
| }) | |
| .join('\n'), | |
| ); | |
| }, | |
| imageSequencePattern: frameNamePattern, | |
| outputDir: frameOutputDir, | |
| frameRange: [startFrame ?? 0, endFrame ?? duration - 1], | |
| onFrameUpdate: (rendered, frameIndex, timeTakenPerFrameMs) => { | |
| if (rendered % 10 !== 0 && frameIndex !== duration - 1) return; | |
| const remainingFrames = Math.max(0, duration - frameIndex); | |
| const estimatedMsLeft = remainingFrames * (timeTakenPerFrameMs || 0); | |
| const timeLeftSeconds = formatMsToETA(estimatedMsLeft); | |
| console.log(`Rendered ${frameIndex} of ${duration} in ${timeTakenPerFrameMs?.toFixed(0)}ms Time left ${timeLeftSeconds}`); | |
| }, | |
| cancelSignal: (callback) => { | |
| controller.stop = () => { | |
| console.log('Stopping render studio ssr renderframes process'); | |
| callback(); | |
| } | |
| } | |
| }); | |
| if (!fs.existsSync(outputDir)) { | |
| fs.mkdirSync(outputDir, { recursive: true }); | |
| } | |
| const resultVideo = await stitchFramesToVideo({ | |
| assetsInfo: result.assetsInfo, | |
| ...renderConfig, | |
| ...renderConfig.composition, | |
| audioCodec: 'mp3', | |
| outputLocation: outFile, | |
| verbose: true, | |
| cancelSignal: (callback) => { | |
| controller.stop = () => { | |
| console.log('Stopping render studio ssr stitchFramesToVideo process'); | |
| callback(); | |
| } | |
| } | |
| }) | |
| console.log('resultVideo', resultVideo) | |
| } | |