├── .eslintrc ├── .github └── workflows │ └── render-video.yml ├── .gitignore ├── .prettierrc ├── .vscode └── settings.json ├── README.md ├── bun.lockb ├── package.json ├── remotion.config.ts ├── src ├── Composition.tsx ├── Root.tsx └── index.ts └── tsconfig.json /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@remotion" 3 | } 4 | -------------------------------------------------------------------------------- /.github/workflows/render-video.yml: -------------------------------------------------------------------------------- 1 | on: workflow_dispatch 2 | 3 | name: Render video 4 | jobs: 5 | render: 6 | name: Render video 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@main 10 | - uses: actions/setup-node@main 11 | - run: sudo apt update 12 | - run: sudo apt install ffmpeg 13 | - run: npm i 14 | - run: echo $WORKFLOW_INPUT > input-props.json 15 | env: 16 | WORKFLOW_INPUT: ${{ toJson(github.event.inputs) }} 17 | - run: npm run build -- --props="./input-props.json" 18 | - uses: actions/upload-artifact@v2 19 | with: 20 | name: video.mp4 21 | path: out/video.mp4 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .DS_Store 4 | .env 5 | 6 | # Ignore the output video from Git but not videos you import into src/. 7 | out 8 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "bracketSpacing": false, 4 | "jsxBracketSameLine": false, 5 | "useTabs": true, 6 | "overrides": [ 7 | { 8 | "files": ["*.yml"], 9 | "options": { 10 | "singleQuote": false 11 | } 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.tabSize": 2, 3 | "typescript.tsdk": "node_modules/typescript/lib", 4 | "editor.codeActionsOnSave": { 5 | "source.organizeImports": "never" 6 | }, 7 | "typescript.enablePromptUseWorkspaceTsdk": true 8 | } 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Remotion video 2 | 3 | Turn a URL into a video that scrolls the website to the top and gives a video. Inspired by [this tweet](https://twitter.com/t3dotgg/status/1752314479482945845) 4 | 5 | This is how you could turn ping.gg into a video: 6 | 7 | ```bash 8 | set -e 9 | sudo npm i -g puppeteer-screenshot-cli 10 | puppeteer-screenshot --width 1920 --fullPage 'https://ping.gg/' > screenshot.jpg 11 | export SCREENSHOT=$(curl --upload-file ./screenshot.jpg https://transfer.sh/screenshot.jpg) 12 | rm screenshot.jpg 13 | sudo npm i -g @remotion/cli@4.0.102 14 | remotion render https://website-scroller.vercel.app/ --codec=prores --props="{\"url\": \"$SCREENSHOT\", \"duration\": 5}" website.mov 15 | ``` 16 | 17 | ## Customize 18 | 19 | Fork and customize the `Composition.tsx` file. [Deploy the Remotion Studio](https://www.remotion.dev/docs/studio/deploy-static) for example to Vercel. Replace the URL in the `render` command. 20 | 21 | ## Commands 22 | 23 | **Install Dependencies** 24 | 25 | ```console 26 | bun i 27 | ``` 28 | 29 | **Start Preview** 30 | 31 | ```console 32 | bun start 33 | ``` 34 | 35 | **Render video** 36 | 37 | ```console 38 | bun run build 39 | ``` 40 | 41 | **Upgrade Remotion** 42 | 43 | ```console 44 | npm run upgrade 45 | ``` 46 | 47 | ## Docs 48 | 49 | Get started with Remotion by reading the [fundamentals page](https://www.remotion.dev/docs/the-fundamentals). 50 | 51 | ## Help 52 | 53 | We provide help on our [Discord server](https://discord.gg/6VzzNDwUwV). 54 | 55 | ## Issues 56 | 57 | Found an issue with Remotion? [File an issue here](https://github.com/remotion-dev/remotion/issues/new). 58 | 59 | ## License 60 | 61 | Note that for some entities a company license is needed. [Read the terms here](https://github.com/remotion-dev/remotion/blob/main/LICENSE.md). 62 | -------------------------------------------------------------------------------- /bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JonnyBurger/website-scroller/096bd2841a7f26c13ffdf91203d33dccdb7e8190/bun.lockb -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "website-scroller", 3 | "version": "1.0.0", 4 | "description": "My Remotion video", 5 | "scripts": { 6 | "start": "remotion studio", 7 | "build": "remotion render MyComp out/video.mp4", 8 | "upgrade": "remotion upgrade", 9 | "test": "eslint src --ext ts,tsx,js,jsx && tsc" 10 | }, 11 | "repository": {}, 12 | "license": "UNLICENSED", 13 | "dependencies": { 14 | "@remotion/cli": "4.0.106", 15 | "react": "^18.0.0", 16 | "react-dom": "^18.0.0", 17 | "remotion": "4.0.106", 18 | "zod": "3.22.4" 19 | }, 20 | "devDependencies": { 21 | "@remotion/eslint-config": "4.0.106", 22 | "@types/react": "^18.0.26", 23 | "@types/web": "^0.0.86", 24 | "eslint": "^8.43.0", 25 | "prettier": "^2.8.8", 26 | "typescript": "^4.9.4" 27 | }, 28 | "packageManager": "npm@10.2.3" 29 | } 30 | -------------------------------------------------------------------------------- /remotion.config.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Note: When using the Node.JS APIs, the config file 3 | * doesn't apply. Instead, pass options directly to the APIs. 4 | * 5 | * All configuration options: https://remotion.dev/docs/config 6 | */ 7 | 8 | import {Config} from '@remotion/cli/config'; 9 | 10 | Config.setVideoImageFormat('jpeg'); 11 | Config.setOverwriteOutput(true); 12 | -------------------------------------------------------------------------------- /src/Composition.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | AbsoluteFill, 4 | CalculateMetadataFunction, 5 | Img, 6 | interpolate, 7 | spring, 8 | useCurrentFrame, 9 | useVideoConfig, 10 | } from 'remotion'; 11 | import * as z from 'zod'; 12 | 13 | export const schema = z.object({ 14 | url: z.string(), 15 | duration: z.number(), 16 | }); 17 | 18 | type Schema = z.infer; 19 | type Dimensions = { 20 | width: number | null; 21 | height: number | null; 22 | }; 23 | 24 | export const calculateMetadata: CalculateMetadataFunction< 25 | Schema & Dimensions 26 | > = async ({props}) => { 27 | const parsed = schema.parse(props); 28 | const img = document.createElement('img'); 29 | img.src = parsed.url; 30 | 31 | const dimensions = await new Promise((resolve, reject) => { 32 | img.onload = () => { 33 | resolve({ 34 | width: img.width, 35 | height: img.height, 36 | }); 37 | }; 38 | img.onerror = reject; 39 | }); 40 | 41 | return { 42 | props: { 43 | url: parsed.url, 44 | width: dimensions.width, 45 | height: dimensions.height, 46 | duration: parsed.duration, 47 | }, 48 | durationInFrames: (parsed.duration + 1) * 30, 49 | fps: 30, 50 | }; 51 | }; 52 | 53 | export const MyComposition: React.FC = ({ 54 | url, 55 | width, 56 | height, 57 | duration, 58 | }) => { 59 | if (width === null || height === null) { 60 | throw new Error('Width or height is null'); 61 | } 62 | const frame = useCurrentFrame(); 63 | const { 64 | width: compositionWidth, 65 | height: compositionHeight, 66 | fps, 67 | } = useVideoConfig(); 68 | 69 | const aspectRatio = height / width; 70 | const imageHeight = compositionWidth * aspectRatio; 71 | 72 | const startOffset = -imageHeight + compositionHeight; 73 | 74 | const progress = spring({ 75 | frame, 76 | fps, 77 | config: { 78 | damping: 46, 79 | }, 80 | durationInFrames: duration * fps, 81 | durationRestThreshold: 0.0001, 82 | }); 83 | 84 | const marginTop = interpolate(progress, [0, 1], [startOffset, 0]); 85 | 86 | return ( 87 | 88 | 96 | 97 | ); 98 | }; 99 | -------------------------------------------------------------------------------- /src/Root.tsx: -------------------------------------------------------------------------------- 1 | import {Composition} from 'remotion'; 2 | import {calculateMetadata, MyComposition, schema} from './Composition'; 3 | 4 | export const RemotionRoot: React.FC = () => { 5 | return ( 6 | <> 7 | 21 | 22 | ); 23 | }; 24 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import {registerRoot} from 'remotion'; 2 | import {RemotionRoot} from './Root'; 3 | 4 | registerRoot(RemotionRoot); 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2018", 4 | "module": "commonjs", 5 | "jsx": "react-jsx", 6 | "outDir": "./dist", 7 | "strict": true, 8 | "noEmit": true, 9 | "lib": ["es2015"], 10 | "esModuleInterop": true, 11 | "skipLibCheck": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "noUnusedLocals": true 14 | } 15 | } 16 | --------------------------------------------------------------------------------