@vfrey/vfrey 0.0.1 → 1.0.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +33 -0
- package/README.md +78 -3
- package/accounts.example.json +61 -0
- package/accounts.first-run.json +41 -0
- package/bin/vfrey.mjs +36 -0
- package/integration-tools/README.md +43 -0
- package/integration-tools/image/pillow.md +32 -0
- package/integration-tools/image/playwright.md +33 -0
- package/integration-tools/image/resvg.md +36 -0
- package/integration-tools/image/satori.md +36 -0
- package/integration-tools/image/sharp.md +38 -0
- package/integration-tools/video/blender.md +34 -0
- package/integration-tools/video/ffmpeg.md +41 -0
- package/integration-tools/video/manim.md +37 -0
- package/integration-tools/video/motion-canvas.md +37 -0
- package/integration-tools/video/remotion.md +50 -0
- package/integration-tools/video/revideo.md +32 -0
- package/package.json +74 -6
- package/skills/vfrey-orchestrator/SKILL.md +15 -0
- package/skills/vfrey-orchestrator/channel-video/SKILL.md +41 -0
- package/skills/vfrey-product/SKILL.md +63 -0
- package/skills/vfrey-product/examples/concurrency-vs-parallelism/framey/cook-one.svg +14 -0
- package/skills/vfrey-product/examples/concurrency-vs-parallelism/framey/cook-two.svg +22 -0
- package/skills/vfrey-product/examples/concurrency-vs-parallelism/framey/index.html +70 -0
- package/skills/vfrey-product/examples/concurrency-vs-parallelism/remotion/cook-one.svg +14 -0
- package/skills/vfrey-product/examples/concurrency-vs-parallelism/remotion/cook-two.svg +22 -0
- package/skills/vfrey-product/examples/concurrency-vs-parallelism/remotion/index.tsx +49 -0
- package/skills/vfrey-product/examples/concurrency-vs-parallelism/script.json +15 -0
- package/skills/vfrey-product/examples/octopus-facts/index.html +60 -0
- package/skills/vfrey-product/examples/octopus-facts/octopus.svg +8 -0
- package/skills/vfrey-product/examples/octopus-facts/script.json +14 -0
- package/skills/vfrey-product/push/SKILL.md +15 -0
- package/skills/vfrey-product/scenes/NOTICE +5 -0
- package/skills/vfrey-product/scenes/blocks.md +57 -0
- package/skills/vfrey-product/scenes/craft.md +118 -0
- package/skills/vfrey-product/scenes/motion.md +88 -0
- package/src/analytics/dashboard.ts +109 -0
- package/src/analytics/index.ts +260 -0
- package/src/api/client.ts +282 -0
- package/src/api/commands.ts +330 -0
- package/src/api/config.ts +34 -0
- package/src/api/creators.ts +463 -0
- package/src/api/credentials.ts +152 -0
- package/src/api/mail.ts +333 -0
- package/src/api/topics.ts +207 -0
- package/src/api/types.ts +205 -0
- package/src/cli.ts +467 -0
- package/src/cloud/executor.ts +281 -0
- package/src/cloud/job.ts +106 -0
- package/src/cloud/render-job.ts +187 -0
- package/src/config.ts +309 -0
- package/src/dashboard-link.ts +180 -0
- package/src/dashboard-start.ts +103 -0
- package/src/desk/channel.ts +64 -0
- package/src/desk/cli.ts +216 -0
- package/src/desk/creators-route.ts +129 -0
- package/src/desk/fonts/JetBrainsMono-OFL.txt +93 -0
- package/src/desk/fonts/google-sans-flex-latin.woff2 +0 -0
- package/src/desk/fonts/jetbrains-mono-medium.woff2 +0 -0
- package/src/desk/http.ts +460 -0
- package/src/desk/mail-route.ts +103 -0
- package/src/desk/overlay.ts +112 -0
- package/src/desk/preview.ts +422 -0
- package/src/desk/screens.ts +149 -0
- package/src/desk/secrets.ts +143 -0
- package/src/desk/series.ts +127 -0
- package/src/desk/state.ts +77 -0
- package/src/desk/studio-route.ts +119 -0
- package/src/desk/tick-runner.ts +66 -0
- package/src/desk/tick.ts +56 -0
- package/src/desk/topics-route.ts +41 -0
- package/src/desk/ui/app.css +505 -0
- package/src/desk/ui/app.js +185 -0
- package/src/desk/ui/apple-icon.png +0 -0
- package/src/desk/ui/components.js +79 -0
- package/src/desk/ui/core.js +156 -0
- package/src/desk/ui/favicon.ico +0 -0
- package/src/desk/ui/icon.svg +1 -0
- package/src/desk/ui/icons.js +49 -0
- package/src/desk/ui/index.html +41 -0
- package/src/desk/ui/metrics-math.d.ts +22 -0
- package/src/desk/ui/metrics-math.js +135 -0
- package/src/desk/ui/theme.js +5 -0
- package/src/desk/ui/views-creators.js +209 -0
- package/src/desk/ui/views-keys.js +114 -0
- package/src/desk/ui/views-mail.js +188 -0
- package/src/desk/ui/views-misc.js +123 -0
- package/src/desk/ui/views-review.js +119 -0
- package/src/desk/ui/views-runs.js +350 -0
- package/src/desk/ui/views-screens.js +163 -0
- package/src/desk/ui/views-series.js +318 -0
- package/src/desk/ui/views-topics.js +87 -0
- package/src/desk/verbs.ts +361 -0
- package/src/doctor.ts +212 -0
- package/src/drain.ts +14 -0
- package/src/engage/index.ts +217 -0
- package/src/generate/index.ts +110 -0
- package/src/generate/script.ts +152 -0
- package/src/generate/title.ts +78 -0
- package/src/help.ts +233 -0
- package/src/home.ts +45 -0
- package/src/llm/client.ts +177 -0
- package/src/log.ts +7 -0
- package/src/oauth/tokens.ts +189 -0
- package/src/oauth/vault.ts +72 -0
- package/src/pipeline/review.ts +642 -0
- package/src/pipeline/run.ts +443 -0
- package/src/pipeline/series-guard.ts +26 -0
- package/src/pipeline/source-fetch.ts +69 -0
- package/src/pipeline/stages.ts +728 -0
- package/src/pipeline/topics-source.ts +90 -0
- package/src/product/commands.ts +296 -0
- package/src/product/config-file.ts +38 -0
- package/src/product/engine-choice.ts +89 -0
- package/src/product/engines.ts +103 -0
- package/src/product/insights.ts +50 -0
- package/src/product/keys.ts +67 -0
- package/src/product/paths.ts +18 -0
- package/src/product/probes.ts +88 -0
- package/src/product/push-flow.ts +90 -0
- package/src/product/render-video.ts +21 -0
- package/src/product/short.ts +62 -0
- package/src/product/update.ts +32 -0
- package/src/project/canonical.ts +14 -0
- package/src/project/commands.ts +177 -0
- package/src/project/dashboard-status.ts +28 -0
- package/src/project/discover.ts +19 -0
- package/src/project/index-file.ts +73 -0
- package/src/project/marker.ts +87 -0
- package/src/project/migrate.ts +201 -0
- package/src/project/names.ts +27 -0
- package/src/project/notice.ts +16 -0
- package/src/project/open.ts +88 -0
- package/src/project/scaffold.ts +112 -0
- package/src/project/templates.ts +42 -0
- package/src/project/videos.ts +64 -0
- package/src/publish/facebook.ts +91 -0
- package/src/publish/index.ts +12 -0
- package/src/publish/instagram.ts +97 -0
- package/src/publish/rate.ts +62 -0
- package/src/publish/session.ts +242 -0
- package/src/publish/tiktok.ts +202 -0
- package/src/publish/youtube.ts +177 -0
- package/src/queue/desk-ops.ts +1170 -0
- package/src/queue/seal.ts +3 -0
- package/src/queue/store.ts +1006 -0
- package/src/remotion/AiLabel.tsx +26 -0
- package/src/remotion/Root.tsx +73 -0
- package/src/remotion/defaults.ts +26 -0
- package/src/remotion/films/claude/ClaudeFilm.tsx +1014 -0
- package/src/remotion/films/engine/EngineMark.tsx +17 -0
- package/src/remotion/films/rea/ReaShort.tsx +552 -0
- package/src/remotion/films/rea/drawings.tsx +907 -0
- package/src/remotion/films/rea/timing.ts +144 -0
- package/src/remotion/index.ts +4 -0
- package/src/remotion/templates/EditorialSplit.tsx +39 -0
- package/src/remotion/templates/KineticType.tsx +39 -0
- package/src/remotion/templates/SignalBoard.tsx +50 -0
- package/src/remotion/timing.ts +22 -0
- package/src/render/chrome.ts +78 -0
- package/src/render/engine-mark.ts +29 -0
- package/src/render/framey-template.ts +130 -0
- package/src/render/html-engine.ts +149 -0
- package/src/render/node-crypto-stub.ts +11 -0
- package/src/render/probe.ts +29 -0
- package/src/render/props.ts +17 -0
- package/src/render/vertical-short.ts +35 -0
- package/src/render/webpack.ts +33 -0
- package/src/render/worker.ts +65 -0
- package/src/scene/assets/fonts/TitanOne-OFL.txt +94 -0
- package/src/scene/assets/fonts/titan-one.woff2 +0 -0
- package/src/scene/assets/gsap/LICENSE.txt +8 -0
- package/src/scene/assets/gsap/gsap.min.js +11 -0
- package/src/scene/audio.ts +54 -0
- package/src/scene/blocks/LICENSE +190 -0
- package/src/scene/blocks/NOTICE +5 -0
- package/src/scene/blocks/bar-chart-race/bar-chart-race.html +585 -0
- package/src/scene/blocks/beat-freeze-cut/beat-freeze-cut.html +874 -0
- package/src/scene/blocks/camcorder-hud/camcorder-hud.html +164 -0
- package/src/scene/blocks/camera-dolly-zoom/camera-dolly-zoom.html +355 -0
- package/src/scene/blocks/code-diff/code-diff.html +817 -0
- package/src/scene/blocks/code-snippet-apple-terminal-pro/code-snippet-apple-terminal-pro.html +248 -0
- package/src/scene/blocks/code-typing/code-typing.html +764 -0
- package/src/scene/blocks/data-chart/data-chart.html +452 -0
- package/src/scene/blocks/flowchart-vertical/flowchart-vertical.html +454 -0
- package/src/scene/blocks/hw-scribble-transition/hw-scribble-transition.html +153 -0
- package/src/scene/blocks/index.json +824 -0
- package/src/scene/blocks/lt-accent-underline/lt-accent-underline.html +107 -0
- package/src/scene/blocks/lt-bold-block/lt-bold-block.html +109 -0
- package/src/scene/blocks/lt-clean-bar/lt-clean-bar.html +112 -0
- package/src/scene/blocks/lt-dark-card/lt-dark-card.html +111 -0
- package/src/scene/blocks/lt-kicker-name/lt-kicker-name.html +114 -0
- package/src/scene/blocks/lt-mask-reveal/lt-mask-reveal.html +120 -0
- package/src/scene/blocks/lt-neon-border/lt-neon-border.html +391 -0
- package/src/scene/blocks/lt-stack-bars/lt-stack-bars.html +109 -0
- package/src/scene/blocks/macos-notification/macos-notification.html +194 -0
- package/src/scene/blocks/mk-background/mk-background.html +236 -0
- package/src/scene/blocks/mk-callout-highlight/mk-callout-highlight.html +183 -0
- package/src/scene/blocks/mk-clone-wall-transition/mk-clone-wall-transition.html +226 -0
- package/src/scene/blocks/mk-line-graph/mk-line-graph.html +293 -0
- package/src/scene/blocks/mk-progress-stat/mk-progress-stat.html +186 -0
- package/src/scene/blocks/mk-specs-list/mk-specs-list.html +223 -0
- package/src/scene/blocks/news-ticker/news-ticker.html +409 -0
- package/src/scene/blocks/organic-light-leak-overlay/organic-light-leak-overlay.html +126 -0
- package/src/scene/blocks/oscilloscope-trace/oscilloscope-trace.html +519 -0
- package/src/scene/blocks/snow/snow.html +205 -0
- package/src/scene/blocks/split-flap-board/split-flap-board.html +347 -0
- package/src/scene/blocks/spotify-card/spotify-card.html +294 -0
- package/src/scene/blocks/starfield/starfield.html +188 -0
- package/src/scene/blocks/thread-message-stack/thread-message-stack.html +239 -0
- package/src/scene/blocks/week-in-merges/week-in-merges.html +470 -0
- package/src/scene/blocks/x-post/x-post.html +437 -0
- package/src/scene/blocks/yt-comment-card/yt-comment-card.html +319 -0
- package/src/scene/blocks/yt-lcd-background/yt-lcd-background.html +307 -0
- package/src/scene/blocks/yt-prism-title/yt-prism-title.html +205 -0
- package/src/scene/blocks.ts +229 -0
- package/src/scene/command.ts +272 -0
- package/src/scene/figure.ts +181 -0
- package/src/scene/glyphs.ts +107 -0
- package/src/scene/init.ts +86 -0
- package/src/scene/inspect/css-tree.d.ts +6 -0
- package/src/scene/inspect/page-scripts.ts +272 -0
- package/src/scene/inspect/report.ts +405 -0
- package/src/scene/inspect/run.ts +200 -0
- package/src/scene/inspect/source.ts +236 -0
- package/src/scene/label.ts +131 -0
- package/src/scene/net-guard.ts +74 -0
- package/src/scene/render.ts +292 -0
- package/src/scene/runtime/browser.ts +199 -0
- package/src/scene/runtime/core.ts +181 -0
- package/src/scene/runtime/react.tsx +115 -0
- package/src/scene/runtime/transitions.ts +133 -0
- package/src/scene/sanitize.ts +181 -0
- package/src/scene/scene.ts +152 -0
- package/src/scene/studio-command.ts +48 -0
- package/src/scene/studio-skin.ts +59 -0
- package/src/scene/studio.ts +337 -0
- package/src/scene/supply.ts +111 -0
- package/src/scene/timing.ts +288 -0
- package/src/scene/transitions/LICENSE +190 -0
- package/src/scene/transitions/NOTICE +5 -0
- package/src/scene/transitions/SOURCE.json +24 -0
- package/src/scene/transitions/transitions.global.js +38 -0
- package/src/scene/transitions.ts +26 -0
- package/src/service/all-projects.ts +153 -0
- package/src/service/commands.ts +256 -0
- package/src/service/due.ts +82 -0
- package/src/service/lock.ts +101 -0
- package/src/service/log-file.ts +56 -0
- package/src/service/state.ts +41 -0
- package/src/service/units.ts +90 -0
- package/src/service/worker.ts +51 -0
- package/src/skill-agents.ts +74 -0
- package/src/skill-install.ts +334 -0
- package/src/sync-write.ts +21 -0
- package/src/tick-report.ts +205 -0
- package/src/tts.ts +124 -0
- package/vendor/@vfrey/contracts/dist/account.d.ts +97 -0
- package/vendor/@vfrey/contracts/dist/account.js +27 -0
- package/vendor/@vfrey/contracts/dist/checks.d.ts +8 -0
- package/vendor/@vfrey/contracts/dist/checks.js +1 -0
- package/vendor/@vfrey/contracts/dist/credentials/crypto.d.ts +4 -0
- package/vendor/@vfrey/contracts/dist/credentials/crypto.js +38 -0
- package/vendor/@vfrey/contracts/dist/domain/advice-persona.d.ts +10 -0
- package/vendor/@vfrey/contracts/dist/domain/advice-persona.js +24 -0
- package/vendor/@vfrey/contracts/dist/domain/backoff.d.ts +13 -0
- package/vendor/@vfrey/contracts/dist/domain/backoff.js +27 -0
- package/vendor/@vfrey/contracts/dist/domain/cadence.d.ts +29 -0
- package/vendor/@vfrey/contracts/dist/domain/cadence.js +95 -0
- package/vendor/@vfrey/contracts/dist/domain/creators.d.ts +146 -0
- package/vendor/@vfrey/contracts/dist/domain/creators.js +2 -0
- package/vendor/@vfrey/contracts/dist/domain/disclosure.d.ts +127 -0
- package/vendor/@vfrey/contracts/dist/domain/disclosure.js +250 -0
- package/vendor/@vfrey/contracts/dist/domain/errors.d.ts +34 -0
- package/vendor/@vfrey/contracts/dist/domain/errors.js +68 -0
- package/vendor/@vfrey/contracts/dist/domain/redact.d.ts +1 -0
- package/vendor/@vfrey/contracts/dist/domain/redact.js +21 -0
- package/vendor/@vfrey/contracts/dist/domain/revenue.d.ts +13 -0
- package/vendor/@vfrey/contracts/dist/domain/revenue.js +16 -0
- package/vendor/@vfrey/contracts/dist/domain/safety.d.ts +20 -0
- package/vendor/@vfrey/contracts/dist/domain/safety.js +61 -0
- package/vendor/@vfrey/contracts/dist/domain/similarity.d.ts +13 -0
- package/vendor/@vfrey/contracts/dist/domain/similarity.js +100 -0
- package/vendor/@vfrey/contracts/dist/domain/templates.d.ts +18 -0
- package/vendor/@vfrey/contracts/dist/domain/templates.js +67 -0
- package/vendor/@vfrey/contracts/dist/domain/tiktok-chunks.d.ts +19 -0
- package/vendor/@vfrey/contracts/dist/domain/tiktok-chunks.js +52 -0
- package/vendor/@vfrey/contracts/dist/domain/topics.d.ts +78 -0
- package/vendor/@vfrey/contracts/dist/domain/topics.js +4 -0
- package/vendor/@vfrey/contracts/dist/domain/types.d.ts +216 -0
- package/vendor/@vfrey/contracts/dist/domain/types.js +1 -0
- package/vendor/@vfrey/contracts/dist/domain/variation.d.ts +57 -0
- package/vendor/@vfrey/contracts/dist/domain/variation.js +278 -0
- package/vendor/@vfrey/contracts/dist/events.d.ts +20 -0
- package/vendor/@vfrey/contracts/dist/events.js +1 -0
- package/vendor/@vfrey/contracts/dist/http.d.ts +11 -0
- package/vendor/@vfrey/contracts/dist/http.js +37 -0
- package/vendor/@vfrey/contracts/dist/index.d.ts +22 -0
- package/vendor/@vfrey/contracts/dist/index.js +22 -0
- package/vendor/@vfrey/contracts/dist/log.d.ts +2 -0
- package/vendor/@vfrey/contracts/dist/log.js +10 -0
- package/vendor/@vfrey/contracts/dist/mail.d.ts +55 -0
- package/vendor/@vfrey/contracts/dist/mail.js +1 -0
- package/vendor/@vfrey/contracts/package.json +18 -0
- package/vendor/@vfrey/ingest/dist/index.d.ts +41 -0
- package/vendor/@vfrey/ingest/dist/index.js +221 -0
- package/vendor/@vfrey/ingest/dist/parse.d.ts +5 -0
- package/vendor/@vfrey/ingest/dist/parse.js +165 -0
- package/vendor/@vfrey/ingest/package.json +21 -0
- package/vendor/framey/LICENSE +190 -0
- package/vendor/framey/NOTICE +15 -0
- package/vendor/framey/SOURCE.json +24 -0
- package/vendor/framey/STUDIO-SOURCE.json +9 -0
- package/vendor/framey/bin/framey-localize-fonts.mjs +12 -0
- package/vendor/framey/bin/framey.mjs +16 -0
- package/vendor/framey/dist/add-VMJD5KBS.js +75 -0
- package/vendor/framey/dist/animatedGifPrep-QPKGJ6PM.js +43 -0
- package/vendor/framey/dist/api.d.ts +211 -0
- package/vendor/framey/dist/api.js +76 -0
- package/vendor/framey/dist/appHistory-WW56FZTT.js +31 -0
- package/vendor/framey/dist/asset-WQKGOELE.js +26 -0
- package/vendor/framey/dist/audio-RSJWVWRA.js +1936 -0
- package/vendor/framey/dist/batchRender-3HPICDCT.js +366 -0
- package/vendor/framey/dist/beats-HL2BDUZI.js +273 -0
- package/vendor/framey/dist/benchmark-RWCWHI5Z.js +230 -0
- package/vendor/framey/dist/browser-ECYET3Q2.js +206 -0
- package/vendor/framey/dist/bundleWithLocalizedFonts-ALYBQEQT.js +52 -0
- package/vendor/framey/dist/captureCompositionFrame-4DA6SEH2.js +94 -0
- package/vendor/framey/dist/catalog-5JVDEUWK.js +979 -0
- package/vendor/framey/dist/catch-up-NEBCL5NO.js +106 -0
- package/vendor/framey/dist/check-F5N544ML.js +111 -0
- package/vendor/framey/dist/checkBrowser-LZ66CMJG.js +110 -0
- package/vendor/framey/dist/chunk-2K7Z2HVP.js +372 -0
- package/vendor/framey/dist/chunk-2UZQHDM7.js +323 -0
- package/vendor/framey/dist/chunk-2X4PQEYM.js +34 -0
- package/vendor/framey/dist/chunk-3422KMTD.js +168 -0
- package/vendor/framey/dist/chunk-3FYD4YEK.js +72 -0
- package/vendor/framey/dist/chunk-3GXQPCJC.js +682 -0
- package/vendor/framey/dist/chunk-3LM6YPBR.js +179 -0
- package/vendor/framey/dist/chunk-442JNXRM.js +20 -0
- package/vendor/framey/dist/chunk-4BNN5FGX.js +182 -0
- package/vendor/framey/dist/chunk-4P7NX5BM.js +11532 -0
- package/vendor/framey/dist/chunk-4UMPIPCQ.js +88 -0
- package/vendor/framey/dist/chunk-4YYJOFKS.js +28 -0
- package/vendor/framey/dist/chunk-54HJ63Q2.js +29 -0
- package/vendor/framey/dist/chunk-5ETJX6WM.js +89 -0
- package/vendor/framey/dist/chunk-5SG54JRS.js +68 -0
- package/vendor/framey/dist/chunk-634HSJ3J.js +58 -0
- package/vendor/framey/dist/chunk-66LREXGS.js +48 -0
- package/vendor/framey/dist/chunk-6BEFEWFB.js +25 -0
- package/vendor/framey/dist/chunk-6BNTGNGD.js +286 -0
- package/vendor/framey/dist/chunk-6DDPOHMK.js +140 -0
- package/vendor/framey/dist/chunk-6GV5XSOR.js +214 -0
- package/vendor/framey/dist/chunk-6JR2XUEE.js +127 -0
- package/vendor/framey/dist/chunk-73QVATIM.js +362 -0
- package/vendor/framey/dist/chunk-7FMUTGOH.js +279 -0
- package/vendor/framey/dist/chunk-7JQFJ6JJ.js +291 -0
- package/vendor/framey/dist/chunk-AHPGAESX.js +113 -0
- package/vendor/framey/dist/chunk-AHX34EFP.js +15 -0
- package/vendor/framey/dist/chunk-AJ5CSAEZ.js +487 -0
- package/vendor/framey/dist/chunk-B7RJV74O.js +5047 -0
- package/vendor/framey/dist/chunk-BL4U6U52.js +254 -0
- package/vendor/framey/dist/chunk-BSQ7XANM.js +210 -0
- package/vendor/framey/dist/chunk-BZ56MTDX.js +6 -0
- package/vendor/framey/dist/chunk-C5BO7PMT.js +282 -0
- package/vendor/framey/dist/chunk-CFUCHO4T.js +358 -0
- package/vendor/framey/dist/chunk-CZGHFATT.js +31 -0
- package/vendor/framey/dist/chunk-DLA44CMS.js +5649 -0
- package/vendor/framey/dist/chunk-DPVXBVLL.js +31292 -0
- package/vendor/framey/dist/chunk-DQBY7FRR.js +397 -0
- package/vendor/framey/dist/chunk-EAMLBAHA.js +154 -0
- package/vendor/framey/dist/chunk-EOVYQ3IG.js +143 -0
- package/vendor/framey/dist/chunk-EVMY7IPL.js +612 -0
- package/vendor/framey/dist/chunk-FG4BBFZI.js +122 -0
- package/vendor/framey/dist/chunk-FI4I7CME.js +56 -0
- package/vendor/framey/dist/chunk-FLREUEHW.js +2081 -0
- package/vendor/framey/dist/chunk-FUFP7A7S.js +1126 -0
- package/vendor/framey/dist/chunk-FV3PIKGH.js +56 -0
- package/vendor/framey/dist/chunk-FXMKHHPO.js +123 -0
- package/vendor/framey/dist/chunk-GGZAM5VH.js +1187 -0
- package/vendor/framey/dist/chunk-GKZEVJIB.js +39 -0
- package/vendor/framey/dist/chunk-GMECXFAN.js +57 -0
- package/vendor/framey/dist/chunk-GNEFDLFB.js +621 -0
- package/vendor/framey/dist/chunk-GPMLSOEG.js +19274 -0
- package/vendor/framey/dist/chunk-GPYVT5EE.js +59 -0
- package/vendor/framey/dist/chunk-H6IEMZEV.js +40 -0
- package/vendor/framey/dist/chunk-HEFQCIXR.js +170 -0
- package/vendor/framey/dist/chunk-HFQ4CWHA.js +118 -0
- package/vendor/framey/dist/chunk-HPDTCOKE.js +304 -0
- package/vendor/framey/dist/chunk-HTUHQKDB.js +548 -0
- package/vendor/framey/dist/chunk-HU2SPFNG.js +103 -0
- package/vendor/framey/dist/chunk-HVW7ABXD.js +19 -0
- package/vendor/framey/dist/chunk-IG7NDKXO.js +26 -0
- package/vendor/framey/dist/chunk-II3J3WXK.js +246 -0
- package/vendor/framey/dist/chunk-IR35BCLK.js +13 -0
- package/vendor/framey/dist/chunk-ISN5GRYI.js +20 -0
- package/vendor/framey/dist/chunk-IUONB55J.js +669 -0
- package/vendor/framey/dist/chunk-IVSUQR2B.js +161 -0
- package/vendor/framey/dist/chunk-IXU6PQSW.js +259 -0
- package/vendor/framey/dist/chunk-IZCW2NKT.js +29 -0
- package/vendor/framey/dist/chunk-JGXJU5PF.js +68 -0
- package/vendor/framey/dist/chunk-JHOCK3DG.js +27 -0
- package/vendor/framey/dist/chunk-JVBRJMOP.js +121 -0
- package/vendor/framey/dist/chunk-K4MJX7SR.js +17 -0
- package/vendor/framey/dist/chunk-K4WKGXRC.js +18 -0
- package/vendor/framey/dist/chunk-KGPLIH7B.js +127 -0
- package/vendor/framey/dist/chunk-KLIKCX6Q.js +63 -0
- package/vendor/framey/dist/chunk-KXTKKBOC.js +61 -0
- package/vendor/framey/dist/chunk-KZI44OPA.js +185 -0
- package/vendor/framey/dist/chunk-LEKZNRKI.js +45 -0
- package/vendor/framey/dist/chunk-LFGJWOVA.js +61 -0
- package/vendor/framey/dist/chunk-LLMSX6V6.js +17 -0
- package/vendor/framey/dist/chunk-LTGTN2TH.js +195 -0
- package/vendor/framey/dist/chunk-M2WBOQ2Q.js +54 -0
- package/vendor/framey/dist/chunk-MI5FTLYK.js +399 -0
- package/vendor/framey/dist/chunk-MLQP2QUF.js +1809 -0
- package/vendor/framey/dist/chunk-MMQ52NIZ.js +956 -0
- package/vendor/framey/dist/chunk-ND46XKK3.js +742 -0
- package/vendor/framey/dist/chunk-NYEXNSHM.js +504 -0
- package/vendor/framey/dist/chunk-O4YRB27L.js +57 -0
- package/vendor/framey/dist/chunk-O7BGSQ5F.js +92 -0
- package/vendor/framey/dist/chunk-OFQWTKPW.js +117 -0
- package/vendor/framey/dist/chunk-P5ZBLXJ6.js +91 -0
- package/vendor/framey/dist/chunk-P6LJ4Y32.js +4662 -0
- package/vendor/framey/dist/chunk-PKZ7RK5N.js +281 -0
- package/vendor/framey/dist/chunk-PURHAXFL.js +15 -0
- package/vendor/framey/dist/chunk-Q5X2B5WT.js +29 -0
- package/vendor/framey/dist/chunk-QAMKOG67.js +248 -0
- package/vendor/framey/dist/chunk-R6YBK4XX.js +514 -0
- package/vendor/framey/dist/chunk-RGLQURIX.js +2997 -0
- package/vendor/framey/dist/chunk-RNV3DM4S.js +3573 -0
- package/vendor/framey/dist/chunk-ROEW726S.js +4638 -0
- package/vendor/framey/dist/chunk-RQVDTPGD.js +6 -0
- package/vendor/framey/dist/chunk-RVZHGOGR.js +26 -0
- package/vendor/framey/dist/chunk-RXATE6FZ.js +385 -0
- package/vendor/framey/dist/chunk-S3S4TGEH.js +424 -0
- package/vendor/framey/dist/chunk-SDYDNHEE.js +11202 -0
- package/vendor/framey/dist/chunk-SSWBU67I.js +173 -0
- package/vendor/framey/dist/chunk-ST35C4K6.js +28 -0
- package/vendor/framey/dist/chunk-SZNTYTIV.js +990 -0
- package/vendor/framey/dist/chunk-T2EUWCJE.js +29 -0
- package/vendor/framey/dist/chunk-T6AGUTZJ.js +444 -0
- package/vendor/framey/dist/chunk-THJALHFJ.js +11131 -0
- package/vendor/framey/dist/chunk-TIQB63MX.js +18128 -0
- package/vendor/framey/dist/chunk-U242RTCT.js +134 -0
- package/vendor/framey/dist/chunk-UJZREAV6.js +3901 -0
- package/vendor/framey/dist/chunk-UMD3V7CU.js +264 -0
- package/vendor/framey/dist/chunk-UOFAE5RF.js +42 -0
- package/vendor/framey/dist/chunk-UTEIUGAZ.js +29 -0
- package/vendor/framey/dist/chunk-VAS6HQX3.js +138 -0
- package/vendor/framey/dist/chunk-VC76R6W2.js +27 -0
- package/vendor/framey/dist/chunk-VFZ7W5W7.js +226 -0
- package/vendor/framey/dist/chunk-VHNLZAE2.js +348 -0
- package/vendor/framey/dist/chunk-VMC4WSH5.js +278 -0
- package/vendor/framey/dist/chunk-VORQIH64.js +108 -0
- package/vendor/framey/dist/chunk-VTXFRIHO.js +186 -0
- package/vendor/framey/dist/chunk-W2LGJSEN.js +182 -0
- package/vendor/framey/dist/chunk-WKBM7NDG.js +32 -0
- package/vendor/framey/dist/chunk-WMQNIQDJ.js +2057 -0
- package/vendor/framey/dist/chunk-WN4XZC5F.js +504 -0
- package/vendor/framey/dist/chunk-WQ6ZXHFQ.js +133 -0
- package/vendor/framey/dist/chunk-X7HBSTCZ.js +53 -0
- package/vendor/framey/dist/chunk-X7MTZ4T2.js +6 -0
- package/vendor/framey/dist/chunk-XPTOJZJF.js +308 -0
- package/vendor/framey/dist/chunk-XT6MXC7A.js +6 -0
- package/vendor/framey/dist/chunk-XT7X6QCP.js +6 -0
- package/vendor/framey/dist/chunk-XTMDWNEC.js +2820 -0
- package/vendor/framey/dist/chunk-XUHOIDKG.js +6 -0
- package/vendor/framey/dist/chunk-XX7LTW5Y.js +138 -0
- package/vendor/framey/dist/chunk-Y3YGTZEG.js +382 -0
- package/vendor/framey/dist/chunk-Y5SBSGN7.js +346 -0
- package/vendor/framey/dist/chunk-YCMVI4MZ.js +625 -0
- package/vendor/framey/dist/chunk-YOR4CTCE.js +261 -0
- package/vendor/framey/dist/chunk-YPRBGITP.js +2748 -0
- package/vendor/framey/dist/chunk-ZKDMAOFU.js +32 -0
- package/vendor/framey/dist/chunk-ZW6DURPF.js +1318 -0
- package/vendor/framey/dist/clean-ZJOGDKDM.js +336 -0
- package/vendor/framey/dist/cli.js +439 -0
- package/vendor/framey/dist/cloudrun-HGJ3B6ZV.js +767 -0
- package/vendor/framey/dist/commands/contrast-audit.browser.js +643 -0
- package/vendor/framey/dist/commands/layout-audit.browser.js +2914 -0
- package/vendor/framey/dist/commands/motion-sample.browser.js +74 -0
- package/vendor/framey/dist/commands/motion-signature.browser.js +475 -0
- package/vendor/framey/dist/compare-W7UY3BAZ.js +463 -0
- package/vendor/framey/dist/compiler-TM3W6GYF.js +136 -0
- package/vendor/framey/dist/component-QF5VGYMU.js +176 -0
- package/vendor/framey/dist/compositions-XT3PMNDF.js +238 -0
- package/vendor/framey/dist/contactSheet-7W6GYHBQ.js +24 -0
- package/vendor/framey/dist/deploy-6FJPWKF2.js +299 -0
- package/vendor/framey/dist/destroy-HE2RIPKZ.js +57 -0
- package/vendor/framey/dist/deterministicFonts-RADD3BSX.js +50 -0
- package/vendor/framey/dist/dist-4S2FGGVY.js +422 -0
- package/vendor/framey/dist/dist-BE4B2EZE.js +32 -0
- package/vendor/framey/dist/dist-EWXJGN63.js +129 -0
- package/vendor/framey/dist/docs-VKIRFEEX.js +131 -0
- package/vendor/framey/dist/doctor-2UNJ2G7U.js +74 -0
- package/vendor/framey/dist/dom-37K6YOH4.js +14 -0
- package/vendor/framey/dist/dotEnv-7PZQTVIM.js +54 -0
- package/vendor/framey/dist/events-ACY3P2PA.js +76 -0
- package/vendor/framey/dist/events-IHB3B2MK.js +69 -0
- package/vendor/framey/dist/ffmpeg-DR5LGGSK.js +63 -0
- package/vendor/framey/dist/figma-BODVUPHB.js +63 -0
- package/vendor/framey/dist/fontData.generated-NXAOI4GH.js +13 -0
- package/vendor/framey/dist/fontLocalizeCli.js +142 -0
- package/vendor/framey/dist/gpuPolicy-XLEXORZY.js +49 -0
- package/vendor/framey/dist/grade-compare-GAXFQ74C.js +661 -0
- package/vendor/framey/dist/gsapParser-IEUSLYDJ.js +2885 -0
- package/vendor/framey/dist/gsapParserAcorn-LZJKCFQI.js +30 -0
- package/vendor/framey/dist/help-VCB5X2HS.js +184 -0
- package/vendor/framey/dist/history-ENGZEVFT.js +471 -0
- package/vendor/framey/dist/hyperframe.manifest.json +22 -0
- package/vendor/framey/dist/hyperframe.runtime.iife.js +1132 -0
- package/vendor/framey/dist/hyperframes-player.global.js +478 -0
- package/vendor/framey/dist/hyperframes-slideshow.global.js +148 -0
- package/vendor/framey/dist/info-WPCJHBMU.js +182 -0
- package/vendor/framey/dist/init-G4WGLYGD.js +1176 -0
- package/vendor/framey/dist/inspect-5IQ6CNEQ.js +106 -0
- package/vendor/framey/dist/installer-ML4HWUV7.js +40 -0
- package/vendor/framey/dist/keyframes-Q6XJO7VW.js +2755 -0
- package/vendor/framey/dist/lambda-A7JC74JF.js +468 -0
- package/vendor/framey/dist/layout-YET6XNXA.js +95 -0
- package/vendor/framey/dist/lint-I7FZN2VJ.js +162 -0
- package/vendor/framey/dist/lintProject-PDB3UN72.js +30 -0
- package/vendor/framey/dist/manager-CD6LJ7ZN.js +45 -0
- package/vendor/framey/dist/manager-WA4F2GST.js +67 -0
- package/vendor/framey/dist/media-treatment-BXYS4WVA.js +1030 -0
- package/vendor/framey/dist/media-use-LWH3MANK.js +113 -0
- package/vendor/framey/dist/models-K363FYM4.js +195 -0
- package/vendor/framey/dist/motionShot-GPAVWXLH.js +640 -0
- package/vendor/framey/dist/normalize-FASIPBUM.js +24 -0
- package/vendor/framey/dist/normalize-audio-VDWYRHN6.js +268 -0
- package/vendor/framey/dist/open-E3TB6P6J.js +128 -0
- package/vendor/framey/dist/parakeetRunner-UP34UOI2.js +58 -0
- package/vendor/framey/dist/pipeline-VXIUYEBU.js +64 -0
- package/vendor/framey/dist/play-BUTDQ4DV.js +353 -0
- package/vendor/framey/dist/policies-OLDYGFK5.js +309 -0
- package/vendor/framey/dist/preflight-2KZ2GWAZ.js +55 -0
- package/vendor/framey/dist/present-GXYSM7NX.js +353 -0
- package/vendor/framey/dist/preview-EKZ33JOJ.js +1910 -0
- package/vendor/framey/dist/producer-SKU5EV3J.js +13 -0
- package/vendor/framey/dist/progress-ZWB4Z4ET.js +76 -0
- package/vendor/framey/dist/projectConfig-3MZAYVTS.js +60 -0
- package/vendor/framey/dist/publication-DTKWNF74.js +33 -0
- package/vendor/framey/dist/remove-background-OY5L7XN2.js +265 -0
- package/vendor/framey/dist/render-CDV4GDFX.js +187 -0
- package/vendor/framey/dist/render-YE5YAMOD.js +2620 -0
- package/vendor/framey/dist/render-batch-ZAMFPCLL.js +274 -0
- package/vendor/framey/dist/renderSetupWorker.js +120 -0
- package/vendor/framey/dist/resolver-D5BIPSEW.js +42 -0
- package/vendor/framey/dist/runtimeVersion.js +18 -0
- package/vendor/framey/dist/sam-WFZXI4F7.js +23 -0
- package/vendor/framey/dist/shaderTransitionWorker.js +44 -0
- package/vendor/framey/dist/sherpa-RQWLV7I3.js +79 -0
- package/vendor/framey/dist/sherpaWorker.js +84 -0
- package/vendor/framey/dist/sites-AMOVABTK.js +54 -0
- package/vendor/framey/dist/skill-ZHVLH7KX.js +15 -0
- package/vendor/framey/dist/slideshow-SJGK4RQS.js +24 -0
- package/vendor/framey/dist/snapshot-TUXL6LBQ.js +1515 -0
- package/vendor/framey/dist/src-JHQGB2S6.js +215 -0
- package/vendor/framey/dist/src-Z5PGEMXB.js +532 -0
- package/vendor/framey/dist/state-B6H3WHCF.js +28 -0
- package/vendor/framey/dist/staticProjectServer-DFVZOUGI.js +63 -0
- package/vendor/framey/dist/studio/assets/hyperframes-player-DLaKjw0L.js +478 -0
- package/vendor/framey/dist/studio/assets/index-BX3KHhGX.js +71 -0
- package/vendor/framey/dist/studio/assets/index-CYbvOVp-.css +1 -0
- package/vendor/framey/dist/studio/assets/index-Dp3QEUGL.js +402 -0
- package/vendor/framey/dist/studio/assets/index-qrTAXbqc.js +531 -0
- package/vendor/framey/dist/studio/favicon.svg +14 -0
- package/vendor/framey/dist/studio/icons/timeline/audio.svg +7 -0
- package/vendor/framey/dist/studio/icons/timeline/captions.svg +5 -0
- package/vendor/framey/dist/studio/icons/timeline/composition.svg +12 -0
- package/vendor/framey/dist/studio/icons/timeline/image.svg +18 -0
- package/vendor/framey/dist/studio/icons/timeline/music.svg +10 -0
- package/vendor/framey/dist/studio/icons/timeline/text.svg +3 -0
- package/vendor/framey/dist/studio/index.html +23 -0
- package/vendor/framey/dist/studio-api-2NECGRMB.js +179 -0
- package/vendor/framey/dist/studioSelectionClient-IUUPKUHZ.js +25 -0
- package/vendor/framey/dist/studioServer-N2OPUMOQ.js +1569 -0
- package/vendor/framey/dist/synthesize-PKTB5ATG.js +183 -0
- package/vendor/framey/dist/system-7F3UEHQT.js +27 -0
- package/vendor/framey/dist/telemetry-DQGPEKM6.js +145 -0
- package/vendor/framey/dist/telemetry-UN55N6U6.js +77 -0
- package/vendor/framey/dist/timeline-LHUUMK63.js +1295 -0
- package/vendor/framey/dist/tokens-OLECWDFK.js +92 -0
- package/vendor/framey/dist/transcribe-O7HQKMIV.js +331 -0
- package/vendor/framey/dist/transcribe-UJESPBT5.js +76 -0
- package/vendor/framey/dist/tts-YCCQHZD4.js +234 -0
- package/vendor/framey/dist/urlDownloader-H2LXLG4U.js +48 -0
- package/vendor/framey/dist/usage-HG4WMM7A.js +2167 -0
- package/vendor/framey/dist/validate-F2XMN6EV.js +87 -0
- package/vendor/framey/dist/vendor/npm/gsap@3.15.0/GSAP-LICENSE.txt +8 -0
- package/vendor/framey/dist/vendor/npm/gsap@3.15.0/dist/CustomEase.min.js +11 -0
- package/vendor/framey/dist/vendor/npm/gsap@3.15.0/dist/MotionPathPlugin.min.js +11 -0
- package/vendor/framey/dist/vendor/npm/gsap@3.15.0/dist/gsap.min.js +11 -0
- package/vendor/framey/dist/warmCheck.js +220 -0
- package/vendor/framey/dist/warmCheckClient.js +139 -0
- package/vendor/framey/dist/workspaceBuildCheck-XYD2YTFW.js +133 -0
- package/vendor/framey-player/LICENSE +190 -0
- package/vendor/framey-player/NOTICE +16 -0
- package/vendor/framey-player/SOURCE.json +6 -0
- package/vendor/framey-player/hyperframes-player.global.js +478 -0
- package/vendor/framey.pin +1 -0
- package/bin/index.js +0 -3
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { mkdtemp, readFile, rm } from "node:fs/promises";
|
|
2
|
+
import http from "node:http";
|
|
3
|
+
import type { AddressInfo } from "node:net";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { linesToBeats, scriptTemplate, validateScript, type ScriptFile } from "../../generate/script.js";
|
|
7
|
+
import { ensureChrome } from "../../render/html-engine.js";
|
|
8
|
+
import { sceneTimingFor } from "../audio.js";
|
|
9
|
+
import { startNetGuard } from "../net-guard.js";
|
|
10
|
+
import { GSAP_CDN, SCENE_FRAME } from "../render.js";
|
|
11
|
+
import { checkScene, SceneError, storeScene } from "../scene.js";
|
|
12
|
+
import { SUPPLY_DIR, writeSupply } from "../supply.js";
|
|
13
|
+
import { COLLECT, HOOK, LOAD_SUBS } from "./page-scripts.js";
|
|
14
|
+
import { buildReport, type InspectReport, type Raw, type SceneFile } from "./report.js";
|
|
15
|
+
import { annotateHtml, parseRules, scanHtml, type Rule } from "./source.js";
|
|
16
|
+
|
|
17
|
+
type Env = Record<string, string | undefined>;
|
|
18
|
+
|
|
19
|
+
export interface InspectOptions {
|
|
20
|
+
dir: string;
|
|
21
|
+
at: number;
|
|
22
|
+
selector?: string | null;
|
|
23
|
+
text?: string | null;
|
|
24
|
+
script?: ScriptFile;
|
|
25
|
+
env?: Env;
|
|
26
|
+
cwd?: string;
|
|
27
|
+
/** Most elements to describe when nothing narrows the query. */
|
|
28
|
+
limit?: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const TYPES: Record<string, string> = {
|
|
32
|
+
".html": "text/html; charset=utf-8",
|
|
33
|
+
".js": "text/javascript",
|
|
34
|
+
".css": "text/css",
|
|
35
|
+
".json": "application/json",
|
|
36
|
+
".svg": "image/svg+xml",
|
|
37
|
+
".png": "image/png",
|
|
38
|
+
".jpg": "image/jpeg",
|
|
39
|
+
".jpeg": "image/jpeg",
|
|
40
|
+
".webp": "image/webp",
|
|
41
|
+
".gif": "image/gif",
|
|
42
|
+
".woff2": "font/woff2",
|
|
43
|
+
".woff": "font/woff",
|
|
44
|
+
".ttf": "font/ttf",
|
|
45
|
+
".mp4": "video/mp4",
|
|
46
|
+
".wav": "audio/wav",
|
|
47
|
+
".mp3": "audio/mpeg",
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
interface Sub {
|
|
51
|
+
src: string;
|
|
52
|
+
css: string;
|
|
53
|
+
scripts: Array<{ text: string; line: number; col: number; file: string }>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** The scene's own files, read once: positions of tags, style rules, and the blocks it mounts. */
|
|
57
|
+
export async function readSceneFiles(dir: string): Promise<{ files: Map<string, SceneFile>; rules: Rule[]; subs: Sub[]; matchers: string[] }> {
|
|
58
|
+
const files = new Map<string, SceneFile>();
|
|
59
|
+
const rules: Rule[] = [];
|
|
60
|
+
const matchers: string[] = [];
|
|
61
|
+
const subs: Sub[] = [];
|
|
62
|
+
const load = async (rel: string): Promise<SceneFile | null> => {
|
|
63
|
+
const abs = path.resolve(dir, rel);
|
|
64
|
+
if (!abs.startsWith(`${dir}${path.sep}`)) return null;
|
|
65
|
+
const source = await readFile(abs, "utf8").catch(() => null);
|
|
66
|
+
if (source === null) return null;
|
|
67
|
+
const file = { label: rel, source, scan: scanHtml(source) };
|
|
68
|
+
files.set(rel, file);
|
|
69
|
+
return file;
|
|
70
|
+
};
|
|
71
|
+
const main = await load("index.html");
|
|
72
|
+
if (!main) throw new SceneError(`The scene folder ${dir} has no index.html.`, "write one, or run `npx vfrey scene init DIR`.", "scene_missing");
|
|
73
|
+
const addRules = (found: Rule[], scope: string | null) => {
|
|
74
|
+
for (const rule of found) {
|
|
75
|
+
const match = scope ? (/#root\b/.test(rule.selector) ? rule.selector.replace(/#root\b/g, scope) : null) : rule.selector;
|
|
76
|
+
if (match === null || (scope && rule.atrule)) continue;
|
|
77
|
+
rules.push(rule);
|
|
78
|
+
matchers.push(match);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
const sheets = async (file: SceneFile, scope: string | null) => {
|
|
82
|
+
for (const block of file.scan.blocks) if (block.kind === "style") addRules(parseRules(block.text, file.label, block, rules.length), scope);
|
|
83
|
+
for (const tag of file.scan.tags) {
|
|
84
|
+
if (tag.name !== "link" || !/stylesheet/i.test(tag.attrs.rel ?? "") || !tag.attrs.href || /^[a-z]+:|^\/\//i.test(tag.attrs.href)) continue;
|
|
85
|
+
const rel = path.posix.normalize(path.posix.join(path.posix.dirname(file.label), tag.attrs.href));
|
|
86
|
+
if (rel.startsWith(`${SUPPLY_DIR}/`) || rel.startsWith("..")) continue;
|
|
87
|
+
const css = await readFile(path.resolve(dir, rel), "utf8").catch(() => null);
|
|
88
|
+
if (css !== null) addRules(parseRules(css, rel, { line: 1, col: 1 }, rules.length), scope);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
await sheets(main, null);
|
|
92
|
+
for (const tag of main.scan.tags) {
|
|
93
|
+
const src = tag.attrs["data-composition-src"];
|
|
94
|
+
if (!src || /^[a-z]+:|^\//i.test(src)) continue;
|
|
95
|
+
const rel = path.posix.normalize(src);
|
|
96
|
+
const sub = await load(rel);
|
|
97
|
+
if (!sub) continue;
|
|
98
|
+
const name = sub.scan.tags.find((t) => t.attrs["data-composition-id"])?.attrs["data-composition-id"] ?? "";
|
|
99
|
+
const scope = `[data-composition-id="${name}"]`;
|
|
100
|
+
const before = rules.length;
|
|
101
|
+
await sheets(sub, scope);
|
|
102
|
+
const css = rules
|
|
103
|
+
.slice(before)
|
|
104
|
+
.map((r, i) => `${matchers[before + i]} { ${r.decls.map((d) => `${d.prop}: ${d.value}${d.important ? " !important" : ""}`).join("; ")} }`)
|
|
105
|
+
.join("\n");
|
|
106
|
+
const scripts = sub.scan.blocks.filter((b) => b.kind === "script" && !b.attrs.src).map((b) => ({ text: b.text, line: b.line, col: b.col, file: rel }));
|
|
107
|
+
subs.push({ src, css, scripts });
|
|
108
|
+
}
|
|
109
|
+
return { files, rules, subs, matchers };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function serve(dir: string): Promise<{ origin: string; close: () => Promise<void> }> {
|
|
113
|
+
const server = http.createServer((req, res) => {
|
|
114
|
+
void (async () => {
|
|
115
|
+
const rel = decodeURIComponent((req.url ?? "/").split(/[?#]/)[0]!).replace(/^\/+/, "") || "index.html";
|
|
116
|
+
const abs = path.resolve(dir, rel);
|
|
117
|
+
if (!abs.startsWith(`${dir}${path.sep}`)) {
|
|
118
|
+
res.writeHead(403).end();
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
try {
|
|
122
|
+
let body: Buffer | string = await readFile(abs);
|
|
123
|
+
const ext = path.extname(abs).toLowerCase();
|
|
124
|
+
if (ext === ".html") {
|
|
125
|
+
const original = body.toString("utf8");
|
|
126
|
+
body = annotateHtml(original, rel);
|
|
127
|
+
if (rel === "index.html") body = body.replace(GSAP_CDN, `<script src="${SUPPLY_DIR}/gsap.min.js"></script>`);
|
|
128
|
+
}
|
|
129
|
+
res.writeHead(200, { "content-type": TYPES[ext] ?? "application/octet-stream", "cache-control": "no-store" }).end(body);
|
|
130
|
+
} catch {
|
|
131
|
+
res.writeHead(404).end("not found");
|
|
132
|
+
}
|
|
133
|
+
})();
|
|
134
|
+
});
|
|
135
|
+
await new Promise<void>((resolve, reject) => {
|
|
136
|
+
server.once("error", reject);
|
|
137
|
+
server.listen(0, "127.0.0.1", resolve);
|
|
138
|
+
});
|
|
139
|
+
return { origin: `http://127.0.0.1:${(server.address() as AddressInfo).port}`, close: () => new Promise<void>((resolve) => server.close(() => resolve())) };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Loads the scene in headless Chrome the way `render --frame` does (network blocked, vfrey's files supplied), seeks to `at`, and reports. */
|
|
143
|
+
export async function inspectScene(options: InspectOptions): Promise<InspectReport> {
|
|
144
|
+
const env = options.env ?? process.env;
|
|
145
|
+
const cwd = options.cwd ?? process.cwd();
|
|
146
|
+
const dir = path.resolve(options.dir);
|
|
147
|
+
const info = await checkScene(dir, "vfrey-framey");
|
|
148
|
+
void info;
|
|
149
|
+
const work = await mkdtemp(path.join(os.tmpdir(), "vfrey-inspect-"));
|
|
150
|
+
const warnings: string[] = [];
|
|
151
|
+
let guard: Awaited<ReturnType<typeof startNetGuard>> | null = null;
|
|
152
|
+
let server: Awaited<ReturnType<typeof serve>> | null = null;
|
|
153
|
+
let browser: { close(): Promise<void> } | null = null;
|
|
154
|
+
try {
|
|
155
|
+
const script = options.script ?? (validateScript(scriptTemplate()) as { ok: true; script: ScriptFile }).script;
|
|
156
|
+
const stored = path.join(work, "scene");
|
|
157
|
+
for (const message of (await storeScene(dir, stored)).warnings) warnings.push(message);
|
|
158
|
+
const timing = await sceneTimingFor({ title: script.title, lines: script.lines, beats: linesToBeats(script.lines).map((b) => ({ role: b.role, text: b.text, caption: b.caption })) }, null, env);
|
|
159
|
+
await writeSupply({ dir: stored, timing, voiceFile: null, kind: "html", env });
|
|
160
|
+
const { files, rules, subs, matchers } = await readSceneFiles(stored);
|
|
161
|
+
const chrome = await ensureChrome(env, cwd);
|
|
162
|
+
guard = await startNetGuard(chrome, path.join(work, "guard"));
|
|
163
|
+
server = await serve(stored);
|
|
164
|
+
const puppeteer = (await import("puppeteer-core")).default;
|
|
165
|
+
const launched = await puppeteer.launch({
|
|
166
|
+
executablePath: guard.chrome,
|
|
167
|
+
headless: "shell",
|
|
168
|
+
defaultViewport: { width: SCENE_FRAME.width, height: SCENE_FRAME.height, deviceScaleFactor: 1 },
|
|
169
|
+
args: ["--no-sandbox", "--disable-gpu", "--hide-scrollbars", "--font-render-hinting=none"],
|
|
170
|
+
});
|
|
171
|
+
browser = launched;
|
|
172
|
+
const page = await launched.newPage();
|
|
173
|
+
await page.evaluateOnNewDocument(HOOK);
|
|
174
|
+
page.on("pageerror", (error) => warnings.push(`the scene threw: ${error instanceof Error ? error.message : String(error)}`));
|
|
175
|
+
await page.goto(`${server.origin}/index.html`, { waitUntil: "load", timeout: 30_000 });
|
|
176
|
+
await page.evaluate(`(${LOAD_SUBS})(${JSON.stringify(subs)})`);
|
|
177
|
+
await page.evaluate("document.fonts.ready.then(() => undefined)");
|
|
178
|
+
await page.evaluate("new Promise((resolve) => setTimeout(resolve, 100))");
|
|
179
|
+
await page.evaluate(COLLECT);
|
|
180
|
+
const limit = options.limit ?? 60;
|
|
181
|
+
const params = { at: options.at, selector: options.selector ?? null, text: options.text ?? null, limit, rules: matchers.map((sel, i) => ({ i, sel })) };
|
|
182
|
+
const raw = (await page.evaluate(`window.__vfCollect(${JSON.stringify(params)})`)) as Raw;
|
|
183
|
+
for (const host of guard.blocked()) warnings.push(`The scene tried to reach ${host} over the network. The network is blocked while a scene renders, so the request failed.`);
|
|
184
|
+
return buildReport({
|
|
185
|
+
raw,
|
|
186
|
+
files,
|
|
187
|
+
rules,
|
|
188
|
+
origin: server.origin,
|
|
189
|
+
scene: path.basename(dir),
|
|
190
|
+
at: options.at,
|
|
191
|
+
query: { selector: options.selector ?? null, text: options.text ?? null },
|
|
192
|
+
warnings,
|
|
193
|
+
});
|
|
194
|
+
} finally {
|
|
195
|
+
await browser?.close().catch(() => undefined);
|
|
196
|
+
await server?.close().catch(() => undefined);
|
|
197
|
+
await guard?.close().catch(() => undefined);
|
|
198
|
+
await rm(work, { recursive: true, force: true });
|
|
199
|
+
}
|
|
200
|
+
}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Source positions for `vfrey scene inspect`: where an element, a CSS declaration or a GSAP call sits in the
|
|
3
|
+
* scene's files. HTML is scanned for tag positions (and annotated with `data-vfsrc` on the same line, so the browser
|
|
4
|
+
* can hand the position back), CSS is parsed with css-tree, and stack traces from the page are turned into file:line:col.
|
|
5
|
+
* The idea (runtime provenance: hook GSAP before the author's scripts and keep the stack of every call) is the one
|
|
6
|
+
* Framey's component inspector uses (packages/inspector at commit 9d89309, Apache-2.0 HeyGen-based). No code is copied from it: it is built
|
|
7
|
+
* for Bun, a bundled page and Framey's runtime, so this is a small independent implementation for vfrey's own scene files.
|
|
8
|
+
*/
|
|
9
|
+
import { generate, parse, walk } from "css-tree";
|
|
10
|
+
|
|
11
|
+
export interface Pos {
|
|
12
|
+
file: string;
|
|
13
|
+
line: number;
|
|
14
|
+
col: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface Tag {
|
|
18
|
+
name: string;
|
|
19
|
+
/** Offset of `<`. */
|
|
20
|
+
at: number;
|
|
21
|
+
/** Offset just after the tag name, where an attribute can be inserted. */
|
|
22
|
+
nameEnd: number;
|
|
23
|
+
line: number;
|
|
24
|
+
col: number;
|
|
25
|
+
attrs: Record<string, string>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface Block {
|
|
29
|
+
kind: "script" | "style";
|
|
30
|
+
text: string;
|
|
31
|
+
/** Position of the first character of `text`. */
|
|
32
|
+
line: number;
|
|
33
|
+
col: number;
|
|
34
|
+
attrs: Record<string, string>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface Scan {
|
|
38
|
+
tags: Tag[];
|
|
39
|
+
blocks: Block[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Line and column (both 1-based) of an offset. */
|
|
43
|
+
export function lineCol(source: string, offset: number): { line: number; col: number } {
|
|
44
|
+
let line = 1;
|
|
45
|
+
let last = -1;
|
|
46
|
+
for (let i = source.indexOf("\n"); i !== -1 && i < offset; i = source.indexOf("\n", i + 1)) {
|
|
47
|
+
line += 1;
|
|
48
|
+
last = i;
|
|
49
|
+
}
|
|
50
|
+
return { line, col: offset - last };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function parseAttrs(text: string): Record<string, string> {
|
|
54
|
+
const attrs: Record<string, string> = {};
|
|
55
|
+
const re = /([^\s"'<>\/=]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/g;
|
|
56
|
+
for (let m = re.exec(text); m; m = re.exec(text)) attrs[m[1]!.toLowerCase()] = m[2] ?? m[3] ?? m[4] ?? "";
|
|
57
|
+
return attrs;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Every opening tag, script and style block of an HTML file, with positions. Comments and raw text are skipped. */
|
|
61
|
+
export function scanHtml(source: string): Scan {
|
|
62
|
+
const tags: Tag[] = [];
|
|
63
|
+
const blocks: Block[] = [];
|
|
64
|
+
let i = 0;
|
|
65
|
+
while (i < source.length) {
|
|
66
|
+
const lt = source.indexOf("<", i);
|
|
67
|
+
if (lt === -1) break;
|
|
68
|
+
if (source.startsWith("<!--", lt)) {
|
|
69
|
+
const close = source.indexOf("-->", lt + 4);
|
|
70
|
+
i = close === -1 ? source.length : close + 3;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
const open = /^<([a-zA-Z][\w:-]*)/.exec(source.slice(lt, lt + 80));
|
|
74
|
+
if (!open) {
|
|
75
|
+
const close = source.indexOf(">", lt);
|
|
76
|
+
i = close === -1 ? source.length : close + 1;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
let j = lt + open[0].length;
|
|
80
|
+
let quote = "";
|
|
81
|
+
for (; j < source.length; j += 1) {
|
|
82
|
+
const c = source[j]!;
|
|
83
|
+
if (quote) {
|
|
84
|
+
if (c === quote) quote = "";
|
|
85
|
+
} else if (c === '"' || c === "'") quote = c;
|
|
86
|
+
else if (c === ">") break;
|
|
87
|
+
}
|
|
88
|
+
const name = open[1]!.toLowerCase();
|
|
89
|
+
const { line, col } = lineCol(source, lt);
|
|
90
|
+
const attrs = parseAttrs(source.slice(lt + open[0].length, j).replace(/\/\s*$/, ""));
|
|
91
|
+
tags.push({ name, at: lt, nameEnd: lt + open[0].length, line, col, attrs });
|
|
92
|
+
i = j + 1;
|
|
93
|
+
if (name === "script" || name === "style") {
|
|
94
|
+
const found = new RegExp(`</${name}\\s*>`, "i").exec(source.slice(i));
|
|
95
|
+
const stop = found ? i + found.index : source.length;
|
|
96
|
+
const start = lineCol(source, i);
|
|
97
|
+
blocks.push({ kind: name, text: source.slice(i, stop), line: start.line, col: start.col, attrs });
|
|
98
|
+
i = found ? stop + found[0].length : source.length;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return { tags, blocks };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** The HTML with ` data-vfsrc="file:line:col"` added to every opening tag. No newline is added, so lines stay the same. */
|
|
105
|
+
export function annotateHtml(source: string, file: string, scan: Scan = scanHtml(source)): string {
|
|
106
|
+
let out = "";
|
|
107
|
+
let last = 0;
|
|
108
|
+
for (const tag of scan.tags) {
|
|
109
|
+
if (tag.name === "script" || tag.name === "style" || tag.name === "html") continue;
|
|
110
|
+
out += `${source.slice(last, tag.nameEnd)} data-vfsrc="${file}:${tag.line}:${tag.col}"`;
|
|
111
|
+
last = tag.nameEnd;
|
|
112
|
+
}
|
|
113
|
+
return out + source.slice(last);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export interface Decl {
|
|
117
|
+
prop: string;
|
|
118
|
+
value: string;
|
|
119
|
+
important: boolean;
|
|
120
|
+
line: number;
|
|
121
|
+
col: number;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export interface Rule {
|
|
125
|
+
/** One selector of the rule's list. */
|
|
126
|
+
selector: string;
|
|
127
|
+
/** The whole selector list as written. */
|
|
128
|
+
selectorText: string;
|
|
129
|
+
/** `@media ...` and the like this rule sits inside. */
|
|
130
|
+
atrule: string | null;
|
|
131
|
+
file: string;
|
|
132
|
+
line: number;
|
|
133
|
+
col: number;
|
|
134
|
+
decls: Decl[];
|
|
135
|
+
/** Position in the stylesheets' order, for the cascade. */
|
|
136
|
+
order: number;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Rules of one stylesheet (the text of a `<style>` block or a CSS file), with positions in the file it came from. */
|
|
140
|
+
export function parseRules(css: string, file: string, start: { line: number; col: number }, order0 = 0): Rule[] {
|
|
141
|
+
const rules: Rule[] = [];
|
|
142
|
+
let ast;
|
|
143
|
+
try {
|
|
144
|
+
ast = parse(css, { positions: true, line: start.line, column: start.col, parseValue: false });
|
|
145
|
+
} catch {
|
|
146
|
+
return rules;
|
|
147
|
+
}
|
|
148
|
+
let order = order0;
|
|
149
|
+
walk(ast, {
|
|
150
|
+
visit: "Rule",
|
|
151
|
+
enter(node: any) {
|
|
152
|
+
if (!node.loc) return;
|
|
153
|
+
const at = (this as any).atrule as { name: string; prelude: unknown } | null;
|
|
154
|
+
if (at && /keyframes$/i.test(at.name)) return;
|
|
155
|
+
const atrule = at ? `@${at.name}${at.prelude ? ` ${generate(at.prelude as never)}` : ""}` : null;
|
|
156
|
+
const decls: Decl[] = [];
|
|
157
|
+
for (let item = node.block.children.head; item; item = item.next) {
|
|
158
|
+
const d = item.data;
|
|
159
|
+
if (d.type === "Declaration" && d.loc) {
|
|
160
|
+
decls.push({ prop: String(d.property).toLowerCase(), value: generate(d.value).trim(), important: Boolean(d.important), line: d.loc.start.line, col: d.loc.start.column });
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
const selectorText = generate(node.prelude);
|
|
164
|
+
const selectors: string[] = [];
|
|
165
|
+
if (node.prelude.type === "SelectorList") for (let s = node.prelude.children.head; s; s = s.next) selectors.push(generate(s.data));
|
|
166
|
+
else selectors.push(selectorText);
|
|
167
|
+
order += 1;
|
|
168
|
+
for (const selector of selectors) rules.push({ selector, selectorText, atrule, file, line: node.loc.start.line, col: node.loc.start.column, decls, order });
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
return rules;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** A selector's specificity as one comparable number (ids, then classes/attributes/pseudo-classes, then types). */
|
|
175
|
+
export function specificity(selector: string): number {
|
|
176
|
+
const s = selector.replace(/"[^"]*"|'[^']*'/g, "").replace(/\[[^\]]*\]/g, ".a");
|
|
177
|
+
const ids = (s.match(/#[\w-]+/g) ?? []).length;
|
|
178
|
+
const classes = (s.match(/\.[\w-]+|:(?!:)[\w-]+/g) ?? []).length;
|
|
179
|
+
const bare = s.replace(/#[\w-]+|\.[\w-]+|::?[\w-]+(\([^)]*\))?/g, " ");
|
|
180
|
+
const types = (bare.match(/(^|[\s>+~(,])[a-zA-Z][\w-]*/g) ?? []).length;
|
|
181
|
+
return ids * 10000 + classes * 100 + types;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** Declarations of a `style="..."` attribute as written. */
|
|
185
|
+
export function inlineDecls(style: string, at: { line: number; col: number }): Decl[] {
|
|
186
|
+
const out: Decl[] = [];
|
|
187
|
+
for (const part of style.split(";")) {
|
|
188
|
+
const colon = part.indexOf(":");
|
|
189
|
+
if (colon <= 0) continue;
|
|
190
|
+
out.push({ prop: part.slice(0, colon).trim().toLowerCase(), value: part.slice(colon + 1).replace(/!important\s*$/i, "").trim(), important: /!important\s*$/i.test(part), line: at.line, col: at.col });
|
|
191
|
+
}
|
|
192
|
+
return out;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export interface Frame {
|
|
196
|
+
fn: string;
|
|
197
|
+
url: string;
|
|
198
|
+
line: number;
|
|
199
|
+
col: number;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** The frames of a V8 stack string. */
|
|
203
|
+
export function parseStack(stack: string): Frame[] {
|
|
204
|
+
const frames: Frame[] = [];
|
|
205
|
+
for (const raw of stack.split("\n")) {
|
|
206
|
+
const m = /^\s*at (?:(.*?) \()?(.*?):(\d+):(\d+)\)?\s*$/.exec(raw);
|
|
207
|
+
if (m) frames.push({ fn: m[1] ?? "", url: m[2]!, line: Number(m[3]), col: Number(m[4]) });
|
|
208
|
+
}
|
|
209
|
+
return frames;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export interface Mapped {
|
|
213
|
+
/** Where the call was written: the innermost frame in a scene file. */
|
|
214
|
+
site: Pos | null;
|
|
215
|
+
/** The vfrey cue (`vfrey.onLine`, `vfrey.at`, ...) that was running, and where the scene called it. */
|
|
216
|
+
cue: { name: string; site: Pos | null } | null;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const CUE = /\.(at|onWord|onLine|captions|block|transition)$/;
|
|
220
|
+
|
|
221
|
+
/** Reads a stack into the creation site and the vfrey cue that ran it. `fileOf` turns a URL into a scene file name, or null. */
|
|
222
|
+
export function mapStack(stack: string, fileOf: (url: string) => string | null): Mapped {
|
|
223
|
+
const frames = parseStack(stack).filter((f) => fileOf(f.url));
|
|
224
|
+
const scene = (f: Frame): Pos | null => {
|
|
225
|
+
const file = fileOf(f.url)!;
|
|
226
|
+
return file.startsWith("vfrey/") ? null : { file, line: f.line, col: f.col };
|
|
227
|
+
};
|
|
228
|
+
let site: Pos | null = null;
|
|
229
|
+
let cue: Mapped["cue"] = null;
|
|
230
|
+
frames.forEach((f, i) => {
|
|
231
|
+
const pos = scene(f);
|
|
232
|
+
if (pos && !site) site = pos;
|
|
233
|
+
if (!pos && CUE.test(f.fn)) cue = { name: `vfrey.${f.fn.split(".").pop()}`, site: frames.slice(i + 1).map(scene).find(Boolean) ?? null };
|
|
234
|
+
});
|
|
235
|
+
return { site, cue };
|
|
236
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { mkdir, rename, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { promisify } from "node:util";
|
|
5
|
+
import { deflateSync, crc32 } from "node:zlib";
|
|
6
|
+
|
|
7
|
+
export const LABEL_TEXT = "AI-GENERATED";
|
|
8
|
+
|
|
9
|
+
type Env = Record<string, string | undefined>;
|
|
10
|
+
|
|
11
|
+
/** A 5 by 7 pixel font for the letters the label uses. The overlay is drawn from these, so it needs no font file and no ffmpeg text filter. */
|
|
12
|
+
const FONT: Record<string, string[]> = {
|
|
13
|
+
A: [".###.", "#...#", "#...#", "#####", "#...#", "#...#", "#...#"],
|
|
14
|
+
I: ["#####", "..#..", "..#..", "..#..", "..#..", "..#..", "#####"],
|
|
15
|
+
"-": [".....", ".....", ".....", "#####", ".....", ".....", "....."],
|
|
16
|
+
G: [".###.", "#...#", "#....", "#.###", "#...#", "#...#", ".###."],
|
|
17
|
+
E: ["#####", "#....", "#....", "####.", "#....", "#....", "#####"],
|
|
18
|
+
N: ["#...#", "##..#", "#.#.#", "#..##", "#...#", "#...#", "#...#"],
|
|
19
|
+
R: ["####.", "#...#", "#...#", "####.", "#.#..", "#..#.", "#...#"],
|
|
20
|
+
T: ["#####", "..#..", "..#..", "..#..", "..#..", "..#..", "..#.."],
|
|
21
|
+
D: ["####.", "#...#", "#...#", "#...#", "#...#", "#...#", "####."],
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const SCALE = 4;
|
|
25
|
+
const PAD_X = 16;
|
|
26
|
+
const PAD_Y = 12;
|
|
27
|
+
const BORDER = 3;
|
|
28
|
+
const SKY: [number, number, number] = [0x90, 0xca, 0xf9];
|
|
29
|
+
const ICE: [number, number, number] = [0xe3, 0xf2, 0xfd];
|
|
30
|
+
|
|
31
|
+
/** Where the label sits: the bottom right corner, the same place the built-in templates put it. */
|
|
32
|
+
export const LABEL_MARGIN = { right: 72, bottom: 56 } as const;
|
|
33
|
+
|
|
34
|
+
export function labelSize(): { width: number; height: number } {
|
|
35
|
+
const textWidth = LABEL_TEXT.length * 6 * SCALE - SCALE;
|
|
36
|
+
return { width: textWidth + PAD_X * 2 + BORDER * 2, height: 7 * SCALE + PAD_Y * 2 + BORDER * 2 };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Top left corner of the label in a 1080 by 1920 frame. */
|
|
40
|
+
export function labelPosition(): { x: number; y: number } {
|
|
41
|
+
const { width, height } = labelSize();
|
|
42
|
+
return { x: 1080 - LABEL_MARGIN.right - width, y: 1920 - LABEL_MARGIN.bottom - height };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** The label as a PNG: black plate, light blue border, pale blue letters. */
|
|
46
|
+
export function labelPng(): Buffer {
|
|
47
|
+
const { width, height } = labelSize();
|
|
48
|
+
const raw = Buffer.alloc((width * 4 + 1) * height);
|
|
49
|
+
const put = (x: number, y: number, rgb: [number, number, number]) => {
|
|
50
|
+
const at = y * (width * 4 + 1) + 1 + x * 4;
|
|
51
|
+
raw[at] = rgb[0];
|
|
52
|
+
raw[at + 1] = rgb[1];
|
|
53
|
+
raw[at + 2] = rgb[2];
|
|
54
|
+
raw[at + 3] = 255;
|
|
55
|
+
};
|
|
56
|
+
for (let y = 0; y < height; y += 1) {
|
|
57
|
+
for (let x = 0; x < width; x += 1) {
|
|
58
|
+
const edge = x < BORDER || y < BORDER || x >= width - BORDER || y >= height - BORDER;
|
|
59
|
+
put(x, y, edge ? SKY : [0, 0, 0]);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
[...LABEL_TEXT].forEach((char, index) => {
|
|
63
|
+
const glyph = FONT[char];
|
|
64
|
+
if (!glyph) return;
|
|
65
|
+
const left = BORDER + PAD_X + index * 6 * SCALE;
|
|
66
|
+
glyph.forEach((row, gy) => {
|
|
67
|
+
[...row].forEach((cell, gx) => {
|
|
68
|
+
if (cell !== "#") return;
|
|
69
|
+
for (let dy = 0; dy < SCALE; dy += 1) for (let dx = 0; dx < SCALE; dx += 1) put(left + gx * SCALE + dx, BORDER + PAD_Y + gy * SCALE + dy, ICE);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
const chunk = (type: string, data: Buffer) => {
|
|
74
|
+
const body = Buffer.concat([Buffer.from(type, "ascii"), data]);
|
|
75
|
+
const out = Buffer.alloc(body.length + 8);
|
|
76
|
+
out.writeUInt32BE(data.length, 0);
|
|
77
|
+
body.copy(out, 4);
|
|
78
|
+
out.writeUInt32BE(crc32(body) >>> 0, body.length + 4);
|
|
79
|
+
return out;
|
|
80
|
+
};
|
|
81
|
+
const header = Buffer.alloc(13);
|
|
82
|
+
header.writeUInt32BE(width, 0);
|
|
83
|
+
header.writeUInt32BE(height, 4);
|
|
84
|
+
header[8] = 8;
|
|
85
|
+
header[9] = 6;
|
|
86
|
+
return Buffer.concat([Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]), chunk("IHDR", header), chunk("IDAT", deflateSync(raw)), chunk("IEND", Buffer.alloc(0))]);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const run = promisify(execFile);
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* The last pass over every scene render: the AI label is overlaid on the finished video with ffmpeg, so nothing the
|
|
93
|
+
* scene draws can hide it. The audio is copied untouched.
|
|
94
|
+
*/
|
|
95
|
+
export async function burnLabel(video: string, workDir: string, env: Env): Promise<void> {
|
|
96
|
+
await mkdir(workDir, { recursive: true });
|
|
97
|
+
const png = path.join(workDir, "ai-label.png");
|
|
98
|
+
const out = path.join(workDir, `labeled${path.extname(video) || ".mp4"}`);
|
|
99
|
+
await writeFile(png, labelPng());
|
|
100
|
+
const { x, y } = labelPosition();
|
|
101
|
+
try {
|
|
102
|
+
await run(
|
|
103
|
+
"ffmpeg",
|
|
104
|
+
["-y", "-v", "error", "-i", video, "-i", png, "-filter_complex", `[0:v][1:v]overlay=${x}:${y}:format=auto,format=yuv420p[v]`, "-map", "[v]", "-map", "0:a?", "-c:v", "libx264", "-preset", "veryfast", "-crf", "17", "-c:a", "copy", "-movflags", "+faststart", out],
|
|
105
|
+
{ env: env as NodeJS.ProcessEnv, timeout: 10 * 60_000, maxBuffer: 8 * 1024 * 1024 },
|
|
106
|
+
);
|
|
107
|
+
await rename(out, video);
|
|
108
|
+
} finally {
|
|
109
|
+
await rm(out, { force: true });
|
|
110
|
+
await rm(png, { force: true });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** The same overlay on a single frame, for `render --frame`. */
|
|
115
|
+
export async function burnLabelStill(image: string, workDir: string, env: Env): Promise<void> {
|
|
116
|
+
await mkdir(workDir, { recursive: true });
|
|
117
|
+
const png = path.join(workDir, "ai-label.png");
|
|
118
|
+
const out = path.join(workDir, `labeled${path.extname(image) || ".png"}`);
|
|
119
|
+
await writeFile(png, labelPng());
|
|
120
|
+
const { x, y } = labelPosition();
|
|
121
|
+
try {
|
|
122
|
+
await run("ffmpeg", ["-y", "-v", "error", "-i", image, "-i", png, "-filter_complex", `[0:v][1:v]overlay=${x}:${y}:format=auto`, "-frames:v", "1", out], {
|
|
123
|
+
env: env as NodeJS.ProcessEnv,
|
|
124
|
+
timeout: 60_000,
|
|
125
|
+
});
|
|
126
|
+
await rename(out, image);
|
|
127
|
+
} finally {
|
|
128
|
+
await rm(out, { force: true });
|
|
129
|
+
await rm(png, { force: true });
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { chmod, mkdir, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import http from "node:http";
|
|
3
|
+
import type { AddressInfo, Socket } from "node:net";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Keeps a scene off the network while it renders. The headless Chrome that renders a scene is started through a
|
|
8
|
+
* small wrapper that points it at a proxy on this machine; the proxy answers every request with a refusal and
|
|
9
|
+
* remembers where the scene tried to go. Pages that vfrey serves are on localhost, which Chrome does not send
|
|
10
|
+
* through a proxy, so the scene itself loads as usual. A request that leaves the machine fails at once, so a scene
|
|
11
|
+
* that waits for one does not hang the render.
|
|
12
|
+
*/
|
|
13
|
+
export interface NetGuard {
|
|
14
|
+
/** The Chrome executable to hand the renderer in place of the real one. */
|
|
15
|
+
chrome: string;
|
|
16
|
+
/** Hosts the scene tried to reach, in the order first seen. */
|
|
17
|
+
blocked(): string[];
|
|
18
|
+
close(): Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function startNetGuard(realChrome: string, dir: string): Promise<NetGuard> {
|
|
22
|
+
const seen = new Map<string, number>();
|
|
23
|
+
const note = (host: string) => seen.set(host, (seen.get(host) ?? 0) + 1);
|
|
24
|
+
const sockets = new Set<Socket>();
|
|
25
|
+
const server = http.createServer((req, res) => {
|
|
26
|
+
try {
|
|
27
|
+
note(new URL(req.url ?? "", "http://unknown").host || "unknown");
|
|
28
|
+
} catch {
|
|
29
|
+
note("unknown");
|
|
30
|
+
}
|
|
31
|
+
res.writeHead(403, { "content-type": "text/plain", connection: "close" });
|
|
32
|
+
res.end("vfrey blocks the network while a scene renders.\n");
|
|
33
|
+
});
|
|
34
|
+
server.on("connect", (req, socket) => {
|
|
35
|
+
note((req.url ?? "unknown").replace(/:443$/, ""));
|
|
36
|
+
socket.end("HTTP/1.1 403 Forbidden\r\nConnection: close\r\n\r\n");
|
|
37
|
+
});
|
|
38
|
+
server.on("connection", (socket) => {
|
|
39
|
+
sockets.add(socket);
|
|
40
|
+
socket.on("close", () => sockets.delete(socket));
|
|
41
|
+
socket.on("error", () => undefined);
|
|
42
|
+
});
|
|
43
|
+
await new Promise<void>((resolve, reject) => {
|
|
44
|
+
server.once("error", reject);
|
|
45
|
+
server.listen(0, "127.0.0.1", resolve);
|
|
46
|
+
});
|
|
47
|
+
const port = (server.address() as AddressInfo).port;
|
|
48
|
+
await mkdir(dir, { recursive: true });
|
|
49
|
+
const wrapper = path.join(dir, "chrome-guard.sh");
|
|
50
|
+
const quote = (value: string) => `'${value.replace(/'/g, `'\\''`)}'`;
|
|
51
|
+
await writeFile(
|
|
52
|
+
wrapper,
|
|
53
|
+
`#!/bin/sh
|
|
54
|
+
# Drop any proxy flag the renderer passes (Remotion asks for no proxy), then force ours.
|
|
55
|
+
for a in "$@"; do
|
|
56
|
+
shift
|
|
57
|
+
case "$a" in
|
|
58
|
+
--no-proxy-server|--proxy-server=*|--proxy-bypass-list=*) ;;
|
|
59
|
+
*) set -- "$@" "$a" ;;
|
|
60
|
+
esac
|
|
61
|
+
done
|
|
62
|
+
exec ${quote(realChrome)} "$@" --proxy-server=http://127.0.0.1:${port}
|
|
63
|
+
`, );
|
|
64
|
+
await chmod(wrapper, 0o755);
|
|
65
|
+
return {
|
|
66
|
+
chrome: wrapper,
|
|
67
|
+
blocked: () => [...seen.keys()],
|
|
68
|
+
async close() {
|
|
69
|
+
for (const socket of sockets) socket.destroy();
|
|
70
|
+
await new Promise<void>((resolve) => server.close(() => resolve()));
|
|
71
|
+
await rm(dir, { recursive: true, force: true });
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
}
|