File size: 2,780 Bytes
08ff55b
f357c45
08ff55b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31a5391
 
08ff55b
 
2811019
 
 
 
 
 
 
 
08ff55b
2811019
08ff55b
2811019
08ff55b
 
 
 
2811019
08ff55b
 
 
a7536b6
08ff55b
 
 
31a5391
f357c45
 
df38a67
 
 
 
 
f357c45
15268b5
 
 
 
 
 
 
 
 
 
 
f357c45
 
 
 
 
 
 
08ff55b
 
 
f357c45
08ff55b
f357c45
 
 
 
 
08ff55b
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
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();

    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) {
      await page.goto(`file://${htmlFile}`, { waitUntil: 'networkidle' });
    } else {
      throw new Error('Either html or htmlFile must be provided.');
    }

    await 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;
    }
  }
}