├── .eslintrc ├── .github └── workflows │ ├── render-video-matrix.yml │ ├── render-video-normally.yml │ └── usage-template.yml ├── .gitignore ├── .prettierrc ├── README.md ├── package.json ├── remotion.config.js └── src ├── Main.jsx ├── Root.jsx └── index.js /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@remotion" 3 | } 4 | -------------------------------------------------------------------------------- /.github/workflows/render-video-matrix.yml: -------------------------------------------------------------------------------- 1 | name: Render video by matrix 2 | run-name: Render video using ${{inputs.num_of_workers}} workers 3 | on: 4 | workflow_call: 5 | inputs: 6 | num_of_workers: 7 | required: true 8 | type: number 9 | remotion_entry_point: 10 | required: true 11 | type: string 12 | remotion_composition_id: 13 | required: true 14 | type: string 15 | 16 | workflow_dispatch: 17 | inputs: 18 | num_of_workers: 19 | description: "How many workers to use?" 20 | required: true 21 | type: number 22 | default: 20 23 | remotion_entry_point: 24 | required: true 25 | type: string 26 | default: src/index.js 27 | remotion_composition_id: 28 | required: true 29 | type: string 30 | default: Main 31 | env: 32 | REMOTION_ENTRY_POINT: ${{inputs.remotion_entry_point}} 33 | REMOTION_COMPOSITION_ID: ${{inputs.remotion_composition_id}} 34 | NUM_OF_WORKERS: ${{inputs.num_of_workers}} 35 | 36 | jobs: 37 | cache-dependencies: 38 | runs-on: ubuntu-latest 39 | steps: 40 | - uses: actions/checkout@v3 41 | 42 | - name: Cache node modules 43 | uses: actions/cache@v3 44 | id: cache-node-modules 45 | with: 46 | path: node_modules 47 | key: ${{ runner.os }}-node-${{ hashFiles('**/package.json') }} 48 | restore-keys: | 49 | ${{ runner.os }}-node- 50 | 51 | - name: Install dependencies 52 | run: npm i 53 | 54 | build-matrix: 55 | runs-on: ubuntu-latest 56 | steps: 57 | - id: set-matrix 58 | run: | 59 | array=($(for i in $(seq 0 $((${{env.NUM_OF_WORKERS}}-1))); do echo "\"$i\""; done)) 60 | array="["$(echo "${array[@]}" | tr ' ' ',')"]" 61 | echo "workers_matrix=$array" >> $GITHUB_OUTPUT 62 | outputs: 63 | workers_matrix: ${{ steps.set-matrix.outputs.workers_matrix }} 64 | 65 | retrieve-total-frames: 66 | runs-on: ubuntu-latest 67 | needs: [cache-dependencies] 68 | steps: 69 | - uses: actions/checkout@v3 70 | - uses: Wandalen/wretry.action@master 71 | with: 72 | action: actions/download-artifact@v3 73 | attempt_limit: 5 74 | attempt_delay: 2000 75 | - uses: FedericoCarboni/setup-ffmpeg@v2 76 | 77 | - name: Restore node modules cache 78 | uses: actions/cache@v3 79 | with: 80 | path: node_modules 81 | key: ${{ runner.os }}-node-${{ hashFiles('**/package.json') }} 82 | restore-keys: | 83 | ${{ runner.os }}-node- 84 | 85 | - id: get-total-frames 86 | run: | 87 | output=$(npx remotion compositions ${{env.REMOTION_ENTRY_POINT}}) 88 | value=$(echo "$output" | awk -v id="${{env.REMOTION_COMPOSITION_ID}}" '$1==id {print $4}' | tail -n 1) 89 | echo "total_frames=$value" >> $GITHUB_OUTPUT 90 | outputs: 91 | total_frames: ${{ steps.get-total-frames.outputs.total_frames }} 92 | 93 | render-audio: 94 | runs-on: ubuntu-latest 95 | needs: [cache-dependencies] 96 | steps: 97 | - uses: actions/checkout@v3 98 | - uses: Wandalen/wretry.action@master 99 | with: 100 | action: actions/download-artifact@v3 101 | attempt_limit: 5 102 | attempt_delay: 2000 103 | 104 | - uses: FedericoCarboni/setup-ffmpeg@v2 105 | 106 | - name: Restore node modules cache 107 | uses: actions/cache@v3 108 | with: 109 | path: node_modules 110 | key: ${{ runner.os }}-node-${{ hashFiles('**/package.json') }} 111 | restore-keys: | 112 | ${{ runner.os }}-node- 113 | 114 | - id: render-audio 115 | run: npx remotion render ${{env.REMOTION_ENTRY_POINT}} ${{env.REMOTION_COMPOSITION_ID}} workflow_output/audio.mp3 116 | 117 | - uses: actions/upload-artifact@v3 118 | with: 119 | name: workflow_output 120 | path: ./workflow_output 121 | 122 | render-videos: 123 | runs-on: ubuntu-latest 124 | needs: [build-matrix, cache-dependencies, retrieve-total-frames] 125 | env: 126 | TOTAL_FRAMES: ${{needs.retrieve-total-frames.outputs.total_frames}} 127 | strategy: 128 | matrix: 129 | worker_id: ${{ fromJson(needs.build-matrix.outputs.workers_matrix) }} 130 | steps: 131 | - uses: actions/checkout@v3 132 | - uses: Wandalen/wretry.action@master 133 | with: 134 | action: actions/download-artifact@v3 135 | attempt_limit: 5 136 | attempt_delay: 2000 137 | - uses: FedericoCarboni/setup-ffmpeg@v2 138 | 139 | - name: Restore node modules cache 140 | uses: actions/cache@v3 141 | with: 142 | path: node_modules 143 | key: ${{ runner.os }}-node-${{ hashFiles('**/package.json') }} 144 | restore-keys: | 145 | ${{ runner.os }}-node- 146 | 147 | - name: Set frames per worker 148 | run: | 149 | framesPerWorker=$((${{env.TOTAL_FRAMES}}/${{env.NUM_OF_WORKERS}})) 150 | echo "FRAMES_PER_WORKER=$framesPerWorker" >> $GITHUB_ENV 151 | 152 | - name: Set start frame 153 | run: echo "START_FRAME=$((${{matrix.worker_id}}*${{env.FRAMES_PER_WORKER}}))" >> $GITHUB_ENV 154 | 155 | - name: Set end frame 156 | run: | 157 | if [ "${{matrix.worker_id}}" -lt "$((${{env.NUM_OF_WORKERS}}-1))" ]; then 158 | echo "END_FRAME=$((${{matrix.worker_id}}*${{env.FRAMES_PER_WORKER}}+${{env.FRAMES_PER_WORKER}}-1))" >> $GITHUB_ENV 159 | else 160 | echo "END_FRAME=$((${{env.TOTAL_FRAMES}}-1))" >> $GITHUB_ENV 161 | fi 162 | 163 | - name: Render muted video 164 | run: npx remotion render ${{env.REMOTION_ENTRY_POINT}} ${{env.REMOTION_COMPOSITION_ID}} workflow_output/${{matrix.worker_id}}.mp4 --frames=${{env.START_FRAME}}-${{env.END_FRAME}} --muted 165 | 166 | - uses: actions/upload-artifact@v3 167 | with: 168 | name: workflow_output 169 | path: ./workflow_output 170 | 171 | merge-videos: 172 | runs-on: ubuntu-latest 173 | needs: [render-videos] 174 | steps: 175 | - uses: actions/checkout@v3 176 | - uses: Wandalen/wretry.action@master 177 | with: 178 | action: actions/download-artifact@v3 179 | attempt_limit: 5 180 | attempt_delay: 2000 181 | - uses: FedericoCarboni/setup-ffmpeg@v2 182 | 183 | - name: create text file with all mp4 files 184 | run: find workflow_output/ -name '*.mp4' | sort -V | xargs printf "file '%s'\n" > list.txt 185 | 186 | - name: merge all mp4 and mp3 files 187 | run: ffmpeg -f concat -i "list.txt" -i workflow_output/audio.mp3 -c copy video.mp4 188 | 189 | - uses: geekyeggo/delete-artifact@v2 190 | with: 191 | name: workflow_output 192 | 193 | - uses: actions/upload-artifact@v3 194 | with: 195 | name: video 196 | path: ./video.mp4 197 | -------------------------------------------------------------------------------- /.github/workflows/render-video-normally.yml: -------------------------------------------------------------------------------- 1 | name: Render video normally 2 | on: 3 | workflow_dispatch: 4 | 5 | jobs: 6 | render: 7 | name: Render video 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@main 11 | - uses: actions/setup-node@main 12 | - uses: FedericoCarboni/setup-ffmpeg@v2 13 | 14 | - run: npm i 15 | - run: npm run build 16 | - uses: actions/upload-artifact@v2 17 | with: 18 | name: video.mp4 19 | path: out/video.mp4 20 | -------------------------------------------------------------------------------- /.github/workflows/usage-template.yml: -------------------------------------------------------------------------------- 1 | name: Usage Template 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | call-workflow-in-another-repo: 8 | uses: yuvraj108c/Remotion-Matrix-Renderer/.github/workflows/render-video-matrix.yml@master 9 | with: 10 | num_of_workers: 10 11 | remotion_composition_id: Main 12 | remotion_entry_point: src/index.js 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .DS_Store 4 | .env 5 | 6 | out 7 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Remotion Matrix Renderer ⚡ 2 | Render [Remotion](https://www.remotion.dev/) videos blazingly fast (up to 6x) using [Github Actions Matrix](https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs/). 3 | 4 | ## Quick Demo 5 | 1. Fork this repository. 6 | 2. Run the `Render video by matrix` workflow under Actions tab. 7 | 3. Set your desired number of workers (Recommended: <50). 8 | 9 | 10 | ## Usage in your project 11 | 1. Call this workflow in your project as shown below. 12 | 2. Specify the required parameters: `num_of_workers`, `remotion_composition_id`, `remotion_entry_point` 13 | 3. The rendered video will be uploaded as an artifact. 14 | 15 | ```YAML 16 | name: Call Remotion-Matrix-Renderer 17 | 18 | on: push 19 | 20 | jobs: 21 | call-workflow-in-another-repo: 22 | uses: yuvraj108c/Remotion-Matrix-Renderer/.github/workflows/render-video-matrix.yml@master 23 | with: 24 | num_of_workers: 10 25 | remotion_composition_id: Main 26 | remotion_entry_point: src/index.js 27 | ``` 28 | ## Benchmarks 29 | 30 | | total_frames | num_of_workers | render_time | 31 | |--------------|----------------|-------------| 32 | | 9000 | 1 | 31m 22s | 33 | | 9000 | 10 | 6m 17s | 34 | | 9000 | 20 | 5m 7s | 35 | | 9000 | 50 | 6m 42s | 36 | | 9000 |>100 | failed | 37 | 38 | ## Limitations 39 | 1. Rendering speed is highly dependent on the number of available github runners. 40 | 2. Rendering on private repositories can consume your free github actions minutes quickly. 41 | 3. Using a high `num_of_workers` (>100) can result in http failures. 42 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "remotion-template-javascript", 3 | "version": "1.0.0", 4 | "description": "My Remotion video", 5 | "scripts": { 6 | "start": "remotion preview", 7 | "build": "remotion render Main out/video.mp4" 8 | }, 9 | "repository": {}, 10 | "license": "UNLICENSED", 11 | "dependencies": { 12 | "@remotion/cli": "^3.3.40", 13 | "react": "^18.2.0", 14 | "react-dom": "^18.2.0", 15 | "remotion": "^3.3.40" 16 | }, 17 | "devDependencies": { 18 | "@remotion/eslint-config": "^3.3.40", 19 | "eslint": "^8.30.0", 20 | "prettier": "^2.8.1" 21 | } 22 | } -------------------------------------------------------------------------------- /remotion.config.js: -------------------------------------------------------------------------------- 1 | // All configuration options: https://remotion.dev/docs/config 2 | // Each option also is available as a CLI flag: https://remotion.dev/docs/cli 3 | // ! The configuration file does only apply if you render via the CLI ! 4 | 5 | import {Config} from 'remotion'; 6 | 7 | Config.setImageFormat('jpeg'); 8 | -------------------------------------------------------------------------------- /src/Main.jsx: -------------------------------------------------------------------------------- 1 | import {Video} from 'remotion'; 2 | 3 | export const Main = () => { 4 | return ( 5 |