Spaces:
Running
Running
| import { chromium } from 'playwright'; | |
| import { attachRecorder } from 'playwright-recorder-plus'; | |
| import fs from 'fs'; | |
| import path from 'path'; | |
| export class HtmlRender { | |
| constructor() { | |
| this.browser = null; | |
| this.page = null; | |
| } | |
| async init() { | |
| if (!this.browser) { | |
| this.browser = await chromium.launch(); | |
| this.page = await this.browser.newPage(); | |
| } | |
| } | |
| async renderFrame(html, htmlFile, outFilePath, options = {}) { | |
| console.log('Rendering frame from', html?.substring(0, 100) || htmlFile, '->', outFilePath) | |
| await this.init(); | |
| if (html) { | |
| await this.page.setContent(html, { waitUntil: 'networkidle' }); | |
| } else if (htmlFile) { | |
| await this.page.goto(`file://${htmlFile}`, { waitUntil: 'networkidle' }); | |
| } else { | |
| throw new Error('Either html or htmlFile must be provided.'); | |
| } | |
| await this.page.screenshot({ path: outFilePath, ...options }); | |
| } | |
| async renderVideo(html, htmlFile, outFilePath, durationSec, options = {}) { | |
| console.log('Rendering video from', html?.substring(0, 100) || htmlFile, '->', outFilePath, "duration", durationSec) | |
| if (!this.browser) { | |
| this.browser = await chromium.launch(); | |
| } | |
| const vw = (options.viewport && options.viewport.width) || 1080; | |
| const vh = (options.viewport && options.viewport.height) || 1920; | |
| const context = await this.browser.newContext({ | |
| viewport: { width: vw, height: vh } | |
| }); | |
| const page = await context.newPage(); | |
| if (html) { | |
| await page.setContent(html, { waitUntil: 'networkidle' }); | |
| } else if (htmlFile) { | |
| const absPath = path.isAbsolute(htmlFile) ? htmlFile : path.join(process.cwd(), htmlFile); | |
| await page.goto(`file://${absPath}`, { waitUntil: 'networkidle' }); | |
| } | |
| // Force a layout/paint | |
| await page.evaluate(() => document.body.offsetHeight); | |
| // Attach high-quality recorder | |
| const recorder = await attachRecorder(page, { | |
| path: outFilePath, | |
| fps: options.fps || 30, | |
| ffmpegOptions: { | |
| // Ensuring high quality for the render farm | |
| crf: 18, | |
| preset: 'veryfast' | |
| } | |
| }); | |
| // Wait for the requested duration | |
| await page.waitForTimeout(durationSec * 1000); | |
| // Stop and finalize | |
| await recorder.stop(); | |
| await recorder.finalized; | |
| await page.close(); | |
| await context.close(); | |
| } | |
| async close() { | |
| if (this.browser) { | |
| await this.browser.close(); | |
| this.browser = null; | |
| this.page = null; | |
| } | |
| } | |
| } | |