File size: 4,345 Bytes
566f223
 
e780fe4
 
 
 
 
 
 
566f223
 
 
 
 
 
 
 
 
 
e780fe4
566f223
 
 
 
 
 
e780fe4
566f223
 
e780fe4
 
566f223
 
 
e780fe4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
566f223
 
 
 
 
 
 
e780fe4
 
566f223
 
e780fe4
566f223
e780fe4
 
 
 
 
 
 
 
 
 
 
 
566f223
e780fe4
 
 
566f223
 
 
 
 
e780fe4
 
 
566f223
 
e780fe4
566f223
 
 
e780fe4
566f223
 
 
 
 
 
 
 
 
 
 
 
e780fe4
566f223
 
 
e780fe4
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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# -*- coding: utf-8 -*-
"""
HWIHWA LAB // Ultra-High-Definition 1:1 Square (720x720) Replay Video Generator
Optimized specifically for Hugging Face Model Hub's Video Preview Sidebar Widget.
- 1:1 Square Aspect Ratio (720x720)
- Huge, bold Cart & Pole dynamics centered in the viewport
- Big shock impulse laser & particle physics
- Clean HUD overlay (Active force, score, shock banner)
- Ultra-sharp H.264 (CRF 17, 25 FPS)
"""

import time
import io
from pathlib import Path
import numpy as np
from PIL import Image
import imageio
from playwright.sync_api import sync_playwright

def generate_square_canvas_replay():
    current_dir = Path(__file__).resolve().parent
    html_url = f"file:///{current_dir.as_posix()}/index.html"
    output_mp4 = current_dir / "replay.mp4"

    raw_frames = []

    print("🚀 Launching Headless Chromium to record 1:1 SQUARE (720x720) CartPole Dynamics...")
    with sync_playwright() as p:
        browser = p.chromium.launch(headless=True)
        # 720x720 Square Viewport with device_scale_factor=1
        context = browser.new_context(viewport={"width": 720, "height": 720}, device_scale_factor=1)
        page = context.new_page()

        page.goto(html_url)
        page.wait_for_timeout(1000)  # Wait for weights & chart initialization

        # Maximize Stage to 100% of 720x720 viewport
        page.evaluate("""() => {
            const header = document.querySelector('.header-bar');
            if (header) header.style.display = 'none';
            const bento = document.querySelector('.bento-deck-golden');
            if (bento) bento.style.display = 'none';
            const labMain = document.querySelector('.lab-main');
            if (labMain) {
                labMain.style.padding = '0';
                labMain.style.height = '100vh';
            }
            const stageSec = document.querySelector('.stage-section');
            if (stageSec) stageSec.style.height = '100vh';
            const area = document.querySelector('.canvas-viewport-area');
            if (area) {
                area.style.height = '100vh';
                area.style.borderRadius = '0';
                area.style.border = 'none';
            }
            window.dispatchEvent(new Event('resize'));
        }""")
        page.wait_for_timeout(300)

        stage = page.locator(".canvas-viewport-area")
        box = stage.bounding_box()
        if not box:
            stage = page.locator("#simCanvas")
            box = stage.bounding_box()

        cx = 360
        cy = 360
        print(f"  🎯 Stage Bounding Box: Width={box['width']}, Height={box['height']}")

        total_frames = 110  # ~4.4 seconds @ 25 FPS
        for i in range(total_frames):
            # Apply dynamic mouse disturbance impulse
            if i == 25:
                page.mouse.move(cx - 20, cy + 50)
                page.mouse.down()
            elif i == 30:
                page.mouse.move(cx + 140, cy - 40)
            elif i == 35:
                page.mouse.up()

            # Optional second gentle tap at frame 70
            if i == 70:
                page.mouse.move(cx + 20, cy + 40)
                page.mouse.down()
            elif i == 74:
                page.mouse.move(cx - 80, cy - 30)
            elif i == 78:
                page.mouse.up()

            screenshot_bytes = stage.screenshot(type="png")
            img = Image.open(io.BytesIO(screenshot_bytes))

            # Resize precisely to 720x720 if slight pixel sub-pixel rounding occurs
            if img.width != 720 or img.height != 720:
                img = img.resize((720, 720), Image.Resampling.LANCZOS)

            raw_frames.append(np.array(img.convert("RGB")))
            time.sleep(0.035)

        browser.close()

    print(f"🎬 Encoding {len(raw_frames)} square frames (720x720) to H.264 replay.mp4...")
    writer = imageio.get_writer(
        str(output_mp4),
        fps=25,
        codec="libx264",
        pixelformat="yuv420p",
        ffmpeg_params=["-crf", "17", "-preset", "slow"]
    )
    for f in raw_frames:
        writer.append_data(f)
    writer.close()

    size_kb = round(output_mp4.stat().st_size / 1024, 1)
    print(f"[✓] SUCCESS: 1:1 Square (720x720) Replay saved to: {output_mp4} ({size_kb} KB)")
    return output_mp4

if __name__ == "__main__":
    generate_square_canvas_replay()