├── .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 |
6 | );
7 | };
8 |
--------------------------------------------------------------------------------
/src/Root.jsx:
--------------------------------------------------------------------------------
1 | import {Composition} from 'remotion';
2 | import {Main} from './Main';
3 |
4 | export const RemotionRoot = () => {
5 | const fps = 30;
6 | return (
7 | <>
8 |
16 | >
17 | );
18 | };
19 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | // This is your entry file! Refer to it when you render:
2 | // npx remotion render HelloWorld out/video.mp4
3 |
4 | import {registerRoot} from 'remotion';
5 | import {RemotionRoot} from './Root';
6 |
7 | registerRoot(RemotionRoot);
8 |
--------------------------------------------------------------------------------