F4L – Automated YouTube Music Distribution
2024End-to-end YouTube distribution tool for indie music labels. Upload a WAV and a cover PNG, fill in title and description — F4L encodes a 1080p video entirely in the browser with FFmpeg WASM, stages it on Google Cloud Storage, and uploads it directly to YouTube via the Data API v3. Zero server-side video processing.
F4L (Friend4Label) is a full-stack automation tool built for Tropical Twista Records — an independent music label with a repetitive YouTube distribution workflow. For every release, the team had to manually open a video editor, loop the cover art for the track's duration, export a 1080p MP4, open YouTube Studio, upload the file, and fill in metadata. F4L eliminates every manual step after file selection: provide a WAV and a cover image, fill in title and description, and the tool handles video encoding, cloud staging, and YouTube upload entirely.
Independent music labels release music frequently but lack the technical staff or time for the manual video creation that YouTube distribution requires. Producing a 'static video' (looped artwork + audio) for each track takes 20–30 minutes of entirely repetitive, fully automatable work. The process scales poorly: a 10-track album means 10 separate manual passes through the same workflow.
Build a single-page tool that: (1) authenticates with Google OAuth2 scoped to YouTube upload; (2) encodes a 1080p video from WAV + PNG entirely in the browser with no server-side video processing; (3) stages the output on GCS and uploads it to YouTube with user-provided metadata; (4) lets the user preview the video before the final YouTube upload step.
- ◈Solo developer — architecture, frontend, backend, OAuth2 integration, YouTube API
- 01FFmpeg compiled to WebAssembly for in-browser video encoding — the most consequential architectural decision. Using @ffmpeg/ffmpeg with the multi-threaded WASM core (core-mt), video encoding runs entirely in the browser: WAV → AAC transcode, PNG → 1080p looped video, then mux. No GPU or CPU cost on the server. Tradeoff: requires SharedArrayBuffer, which requires COOP/COEP headers and the vite-plugin-cross-origin-isolation Vite plugin to satisfy browser security requirements.
- 02GCS as a temporary staging layer — rather than piping video directly from browser to YouTube API, the architecture uses Google Cloud Storage as an intermediary: browser uploads MP4 to Express, which streams to GCS; metadata JSON is uploaded separately; a final submit call tells the backend to pull both from GCS and push to YouTube. This decouples encoding from upload timing and lets the user review the video and fill metadata asynchronously.
- 03UUID-namespaced GCS files — each browser session generates a UUID on mount. All GCS objects (video-<uuid>.mp4, metadata-<uuid>.json) use this UUID as a namespace, preventing collision across concurrent users or multiple open tabs with zero backend coordination.
- 04Duration extraction via FFmpeg log parsing — to determine video length for the -loop 1 image-to-video command, the code runs ffmpeg -i inputAd.wav intentionally to trigger info output, captures the log event stream, and parses 'Duration: HH:MM:SS.xx' with a regex. An unofficial but reliable WASM media metadata pattern.
- 05Manual OAuth2 authorization code flow after iterating through 3 auth approaches — the codebase contains traces of passport-google-oauth20, @react-oauth/google, and Google Identity Services HTML buttons. The final solution uses a raw server-side authorization code exchange, giving full control over token storage, session management, and scope configuration.
- 06Exponential backoff on user data fetch — the login() function implements a manual retry loop (up to 5 retries, starting at 1s, doubling each attempt) to handle the race condition between Firestore write completion and the immediate GET /getuser call from the frontend.
- 07In-memory user cache on the backend — the backend writes user profiles to Firestore (persistent) and also caches them as a module-level variable in memory. GET /getuser reads from memory, not Firestore, avoiding a database round-trip on every session hydration at the cost of losing the cache on server restart.
- →FFmpeg WASM is production-viable for light-to-medium video encoding in the browser, but COOP/COEP header requirements for SharedArrayBuffer need careful coordination between the Vite dev server config and the Express production server — a misconfiguration silently falls back to single-threaded mode with no warning.
- →The GCS staging layer adds one network hop but provides a meaningful UX win: encoding and metadata input are fully decoupled, so the user can review the video and fill in the description while the file is still uploading — the flow feels more forgiving than a single atomic submit.
- →Iterating through multiple auth libraries before landing on a manual OAuth2 flow was time-consuming but produced a much deeper understanding of the token exchange, scope negotiation, and session lifecycle — which made debugging the final production auth more tractable.
- →UUID-scoped file storage is a simple but robust isolation strategy for multi-user tools: it requires no session synchronization on the backend and is trivially testable — just check that two concurrent sessions produce different GCS object paths.
- →Parsing media metadata from FFmpeg log output is fragile but pragmatic for WAV inputs where the duration is always present in a predictable format. For a production-grade tool handling multiple container formats, a dedicated metadata extraction pass (e.g. ffprobe) would be more robust.
Feature-complete — built for Tropical Twista Records