{"slug": "i-built-a-local-video-processing-workstation-with-ai-here-s-the-complete-journey", "title": "I Built a Local Video Processing Workstation with AI — Here's the Complete Journey", "summary": "A developer built ClipForge, a cross-platform desktop video processing app using Electron, React, FFmpeg, and Claude Code. The app handles 20+ video/audio operations locally with single, stack, and batch modes. The developer solved challenges like FFmpeg integration, real-time preview via canvas, and Electron packaging across Windows, macOS, and Linux.", "body_md": "From idea to release in 3 weeks, using Claude Code to build ClipForge — a cross-platform desktop app powered by Electron and FFmpeg.\n\nMost video processing tools force you to:\n\nI wanted a **fully local, feature-rich, good-looking** video processing tool. So I built ClipForge.\n\nClipForge is a desktop app that handles 20+ video/audio operations locally:\n\nThree modes: Single operation, Stack (chain multiple ops), Batch processing.\n\nBuilt with Electron, React, FFmpeg, and Zustand. Ships for Windows, macOS, and Linux.\n\n| Layer | Tech | Why |\n|---|---|---|\n| Desktop | Electron 42 | Cross-platform, Node.js for FFmpeg |\n| UI | React 18 + TypeScript | Component ecosystem |\n| Build | Vite 5 + Electron Forge | Fast HMR, clean packaging |\n| State | Zustand | Simple, no boilerplate |\n| Styling | Tailwind CSS | Rapid UI development |\n| Video | FFmpeg (bundled) | Industry-standard processing |\n| AI | Claude Code | Pair programming assistant |\n\n```\nnpm create electron-app clipforge\n```\n\nElectron Forge generated the boilerplate: main process, preload script, renderer with Vite.\n\nBuilt a 4-panel layout:\n\nDark theme with Tailwind CSS.\n\nThis is the core challenge — wrapping FFmpeg's CLI into visual operations.\n\n**Architecture:**\n\n```\nRenderer (React)\n    │  invoke('process:start', request)\n    ▼\nPreload (IPC bridge)\n    │\n    ▼\nMain Process (Node.js)\n    │  composeArgs(request) → ffmpeg args array\n    ▼\nFFmpeg (child_process.spawn)\n    │  progress parsing from stderr\n    ▼\nEvents back to renderer\n```\n\n**Example: Watermark Removal**\n\nInstead of FFmpeg's `delogo`\n\nfilter (which has boundary restrictions — x≥1, y≥1, no edge support), I used a crop + blur + overlay approach:\n\n``` js\ncase 'delogo': {\n  const x = Math.max(0, Math.round(Number(p.x) || 0));\n  const y = Math.max(0, Math.round(Number(p.y) || 0));\n  const w = Math.max(10, Math.round(Number(p.w) || 10));\n  const h = Math.max(10, Math.round(Number(p.h) || 10));\n\n  args.push('-filter_complex',\n    `[0:v]split[a][b];` +\n    `[b]crop=${w}:${h}:${x}:${y},gblur=sigma=30,format=rgba,colorchannelmixer=aa=0.7[b2];` +\n    `[a][b2]overlay=${x}:${y}[out]`\n  );\n  args.push('-map', '[out]', '-map', '0:a?');\n  args.push(...videoCodec(outExt));\n  break;\n}\n```\n\nThe filter graph:\n\n`crop`\n\n— extract the watermark region`gblur`\n\n— Gaussian blur (more natural than boxblur)`colorchannelmixer=aa=0.7`\n\n— semi-transparent blend for smooth integrationUsers need to see changes immediately, not after processing completes.\n\n**Solution:** Canvas-based preview simulation. Instead of running FFmpeg, read frames from the `<video>`\n\nelement and apply operations on a `<canvas>`\n\n:\n\n``` js\nuseEffect(() => {\n  const render = () => {\n    drawPreview(ctx, video, previewOps, { width: rect.width, height: rect.height });\n  };\n  render(); // immediate draw\n\n  if (playing) {\n    const loop = () => { render(); raf = requestAnimationFrame(loop); };\n    raf = requestAnimationFrame(loop);\n  }\n  return () => cancelAnimationFrame(raf);\n}, [playing, playhead, JSON.stringify(previewOps)]);\n```\n\nAdjusting brightness, crop region, or rotation shows instant feedback.\n\nFor watermark removal, users drag to select the area. Screen coordinates must convert to video pixel coordinates (accounting for letterbox scaling):\n\n```\nfunction screenToVideo(localX, localY, container, videoW, videoH) {\n  const { scale, ox, oy } = getVideoMapping(container, videoW, videoH);\n  return {\n    x: Math.max(0, Math.min(Math.round((localX - ox) / scale), videoW)),\n    y: Math.max(0, Math.min(Math.round((localY - oy) / scale), videoH)),\n  };\n}\n```\n\n**Bug I hit:** The `onUp`\n\ncallback captured stale state from `useState`\n\n. Fixed by using `useRef`\n\nfor live coordinates during drag.\n\nElectron packaging is tricky — FFmpeg binaries can't go inside the asar archive, and Linux needs lowercase executable names.\n\n**forge.config.ts:**\n\n```\npackagerConfig: {\n  asar: { unpackDir: 'src/main/ffmpeg' },\n  extraResource: ['src/main/ffmpeg'],\n  executableName: 'clipforge',\n}\n```\n\n**GitHub Actions** builds all three platforms in parallel:\n\n```\njobs:\n  build:\n    strategy:\n      matrix:\n        os: [macos-latest, ubuntu-latest, windows-latest]\n    runs-on: ${{ matrix.os }}\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-node@v4\n      - run: npm ci\n      - run: npm run make\n```\n\nPush a tag → auto-build → auto-publish to GitHub Releases.\n\nClaude Code handled tedious work (Electron packaging, FFmpeg arg mapping, IPC boilerplate), but I still needed to:\n\nGot \"open file → select operation → process → output\" working before adding preview, batch mode, or i18n.\n\nBinary files, asar compression, platform-specific naming — expect to spend time here. Automate with CI early.\n\nLicense: MIT + Commons Clause (free for personal use, commercial use requires authorization).\n\n*Built with Electron, FFmpeg, and a lot of help from AI. The future of indie development is here.*", "url": "https://wpnews.pro/news/i-built-a-local-video-processing-workstation-with-ai-here-s-the-complete-journey", "canonical_source": "https://dev.to/mayu888/i-built-a-local-video-processing-workstation-with-ai-heres-the-complete-journey-413l", "published_at": "2026-06-26 08:49:46+00:00", "updated_at": "2026-06-26 09:03:44.923499+00:00", "lang": "en", "topics": ["developer-tools", "artificial-intelligence", "ai-tools"], "entities": ["ClipForge", "Electron", "React", "FFmpeg", "Claude Code", "Zustand", "Tailwind CSS", "Vite"], "alternates": {"html": "https://wpnews.pro/news/i-built-a-local-video-processing-workstation-with-ai-here-s-the-complete-journey", "markdown": "https://wpnews.pro/news/i-built-a-local-video-processing-workstation-with-ai-here-s-the-complete-journey.md", "text": "https://wpnews.pro/news/i-built-a-local-video-processing-workstation-with-ai-here-s-the-complete-journey.txt", "jsonld": "https://wpnews.pro/news/i-built-a-local-video-processing-workstation-with-ai-here-s-the-complete-journey.jsonld"}}