Skip to content

Commit 303a643

Browse files
authored
Merge pull request #3 from btbishop93/feat/ffmpeg-wasm-v2
feat: migrate to client-side ffmpeg.wasm for Vercel compatibility
2 parents 6dae5f2 + da61651 commit 303a643

9 files changed

Lines changed: 428 additions & 259 deletions

File tree

README.md

Lines changed: 19 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -9,54 +9,26 @@ Press your app preview videos into App Store perfection.
99
- Convert videos to macOS App Store format (1920×1080)
1010
- Convert videos to iOS App Store format (886×1920)
1111
- Add silent audio track (fixes common Apple upload rejections)
12-
- Fast server-side processing with FFmpeg
12+
- **100% client-side processing** - your video never leaves your browser
1313
- Desktop-only (video processing requires desktop browser)
1414

1515
## Requirements
1616

1717
### System Requirements
1818

19-
- **Node.js** 18.x or higher
20-
- **FFmpeg** installed on the server (required for video processing)
19+
- **Node.js** 18.x or higher (for development/hosting)
20+
- **Modern browser** with SharedArrayBuffer support (Chrome, Firefox, Edge)
2121

2222
### Video Requirements
2323

2424
- Format: MP4
25-
- Duration: 15-30 seconds
26-
- Max file size: 500MB
25+
- Duration: 15-30 seconds (App Store limit)
2726

28-
## Server Requirements
27+
## How It Works
2928

30-
This application requires FFmpeg to be installed on the server.
29+
Ciderpress uses [ffmpeg.wasm](https://github.com/ffmpegwasm/ffmpeg.wasm) to process videos entirely in your browser using WebAssembly. No server-side processing required.
3130

32-
### macOS
33-
34-
```bash
35-
brew install ffmpeg
36-
ffmpeg -version
37-
```
38-
39-
### Ubuntu/Debian
40-
41-
```bash
42-
sudo apt update
43-
sudo apt install -y ffmpeg
44-
ffmpeg -version
45-
```
46-
47-
### CentOS/RHEL/Fedora
48-
49-
```bash
50-
sudo dnf install -y ffmpeg ffmpeg-devel
51-
ffmpeg -version
52-
```
53-
54-
### Windows
55-
56-
1. Download FFmpeg from [ffmpeg.org/download.html](https://www.ffmpeg.org/download.html)
57-
2. Extract to `C:\ffmpeg`
58-
3. Add `C:\ffmpeg\bin` to system PATH
59-
4. Verify: `ffmpeg -version`
31+
The first time you convert a video, the app downloads the FFmpeg WASM core (~31MB). This is cached by your browser for future use.
6032

6133
## Development
6234

@@ -89,14 +61,13 @@ bun run build
8961
- **Animation:** Motion (Framer Motion)
9062
- **Linting:** Biome
9163
- **Testing:** Vitest + React Testing Library
92-
- **Video Processing:** FFmpeg (server-side)
64+
- **Video Processing:** ffmpeg.wasm (client-side WebAssembly)
9365

9466
## Project Structure
9567

9668
```
9769
src/
9870
├── app/ # Next.js App Router
99-
│ ├── api/convert/ # Video conversion API endpoint
10071
│ └── page.tsx # Main page
10172
├── components/
10273
│ ├── providers/ # React context providers
@@ -110,22 +81,22 @@ src/
11081

11182
## Deployment
11283

113-
Deploy to any Node.js platform that supports Next.js:
84+
Deploy to any static hosting or Node.js platform:
11485

115-
- Vercel
116-
- Railway
117-
- Render
118-
- DigitalOcean App Platform
119-
- AWS (EC2, ECS, Lambda)
86+
- Vercel (recommended)
87+
- Netlify
88+
- Cloudflare Pages
89+
- Any static host
12090

121-
**Important:** Ensure FFmpeg is installed on your deployment server. The app will return a 503 error if FFmpeg is not available.
91+
No special server configuration required since all video processing happens client-side.
12292

123-
### Environment Variables
93+
### Required Headers
12494

125-
No environment variables are required for basic operation. For production, consider:
95+
The app requires these security headers for SharedArrayBuffer support (already configured in `next.config.mjs`):
12696

127-
```env
128-
NODE_ENV=production
97+
```
98+
Cross-Origin-Embedder-Policy: require-corp
99+
Cross-Origin-Opener-Policy: same-origin
129100
```
130101

131102
## License

bun.lock

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

next.config.mjs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
/** @type {import('next').NextConfig} */
22
const nextConfig = {
3-
// Enable Turbopack (default in Next.js 16)
4-
turbopack: {},
5-
6-
// Set security headers for SharedArrayBuffer support
3+
// Enable WebAssembly support for ffmpeg.wasm
4+
webpack: (config) => {
5+
config.experiments = {
6+
...config.experiments,
7+
asyncWebAssembly: true,
8+
};
9+
return config;
10+
},
11+
12+
// Set security headers for SharedArrayBuffer support (required for ffmpeg.wasm)
713
async headers() {
814
return [
915
{

package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
},
88
"packageManager": "bun@1.2.20",
99
"scripts": {
10-
"dev": "next dev",
11-
"build": "next build",
10+
"dev": "next dev --webpack",
11+
"build": "next build --webpack",
1212
"start": "next start",
1313
"lint": "biome lint ./src",
1414
"format": "biome format --write ./src",
@@ -20,6 +20,8 @@
2020
"test:coverage": "vitest run --coverage"
2121
},
2222
"dependencies": {
23+
"@ffmpeg/ffmpeg": "^0.12.15",
24+
"@ffmpeg/util": "^0.12.2",
2325
"@radix-ui/react-checkbox": "^1.1.4",
2426
"@radix-ui/react-dropdown-menu": "^2.1.6",
2527
"@radix-ui/react-label": "^2.1.2",

src/app/api/convert/route.ts

Lines changed: 0 additions & 175 deletions
This file was deleted.

src/components/terminal-content.tsx

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -188,19 +188,42 @@ export default function TerminalContent({
188188
return null;
189189
}
190190

191+
const textClassName = cn(
192+
"break-words w-full",
193+
message.type === "prompt" && "text-cyan-400",
194+
message.type === "info" && "text-neutral-400",
195+
message.type === "success" && "text-emerald-400",
196+
message.type === "error" && "text-red-400",
197+
);
198+
199+
// Live messages render instantly and update in real-time
200+
if (message.live) {
201+
// Auto-complete live messages so the queue keeps moving
202+
if (!isComplete) {
203+
handleMessageComplete(index);
204+
}
205+
206+
return (
207+
<div key={`${messageVersion}-${index}-live`}>
208+
<span className={cn("text-sm font-mono tracking-tight", textClassName)}>
209+
{message.text}
210+
</span>
211+
{message.buttons && (
212+
<AnimatedSpan delay={TERMINAL_TIMING.BUTTON_APPEAR_DELAY_MS}>
213+
{renderButtons(message.buttons, index)}
214+
</AnimatedSpan>
215+
)}
216+
</div>
217+
);
218+
}
219+
191220
return (
192221
<div key={`${messageVersion}-${index}`}>
193222
<TypingAnimation
194223
delay={0} // No delay - we control start via activeMessageIndex
195224
duration={TERMINAL_TIMING.TYPING_SPEED_MS}
196225
onComplete={() => handleMessageComplete(index)}
197-
className={cn(
198-
"break-words w-full",
199-
message.type === "prompt" && "text-cyan-400",
200-
message.type === "info" && "text-neutral-400",
201-
message.type === "success" && "text-emerald-400",
202-
message.type === "error" && "text-red-400",
203-
)}
226+
className={textClassName}
204227
>
205228
{message.text}
206229
</TypingAnimation>

0 commit comments

Comments
 (0)