Skip to content

feat: audio preview service — generate 30-second clips from IPFS audio #38

Description

@grantfox-oss

Summary

Add a GET /api/ipfs/:cid/preview endpoint that fetches audio from IPFS, trims it to a 30-second preview clip, and streams the result back with proper audio headers. Buyers should be able to preview beats before purchasing without downloading the full file.

Why

  • The frontend SampleDetail page has a play button but no actual audio to play. The IPFS CID points to the full beat (often 3-10MB). Downloading the full file for a preview is wasteful and slow.
  • Issue feat: add GET /api/ipfs/:cid/preview endpoint for 30-second audio clips #6 was closed but the endpoint doesn't exist in the current codebase. The src/routes/ipfs.ts file only has a POST /upload route.
  • Preview clips (~300KB for 30s of MP3) load in under a second on mobile. Full beats take 5-15 seconds on 3G. In the target markets (West Africa, East Africa), this is the difference between a sale and a bounce.

What to build

1. Preview endpoint (GET /api/ipfs/:cid/preview)

GET /api/ipfs/:cid/preview?start=0&duration=30
Range: bytes=0-   (optional, for seeking)
  • Validates CID format (same regex as upload route)
  • Fetches the full audio from IPFS gateway (Pinata or public gateway)
  • Pipes through fluent-ffmpeg (or @ffmpeg.wasm/core for zero native deps) to trim to 30 seconds
  • Streams the result back with Content-Type: audio/mpeg, Accept-Ranges: bytes
  • Supports Range header for seeking (audio players need this)
  • Cache-Control: public, max-age=86400 (preview clips are immutable per CID)

2. FFmpeg processing pipeline

export async function generatePreview(
  inputStream: Readable,
  opts: { start?: number; duration: number; format: 'mp3' | 'aac' }
): Readable
  • Uses fluent-ffmpeg with ffmpeg-static binary (no system ffmpeg required)
  • Input: any audio format the contract supports (mp3, wav, flac, aiff, ogg)
  • Output: MP3, 128kbps, mono (small file, good enough for preview)
  • Start offset: configurable, default 30 seconds in (skips intro silence for most beats)
  • Duration: configurable, default 30 seconds
  • Error handling: if ffmpeg fails (corrupt file, unsupported format), return 422 with clear message

3. Caching layer

  • Cache generated previews in memory (LRU cache, max 100 entries, ~30MB total)
  • Cache key: ${cid}:${start}:${duration}
  • On cache hit: stream from memory, skip ffmpeg entirely
  • On cache miss: generate, cache, stream
  • Optional: cache to disk (/tmp/crate-previews/) for persistence across restarts
  • Cache eviction: LRU with max age of 1 hour (previews are cheap to regenerate)

4. Streaming response

  • Don't buffer the full preview in memory — pipe ffmpeg output directly to res
  • Use res.writeHead(200, { 'Content-Type': 'audio/mpeg', 'Transfer-Encoding': 'chunked' })
  • Support Range requests: parse range header, seek ffmpeg to the right byte offset
  • Abort ffmpeg if client disconnects (save CPU)

5. Fallback for non-audio CIDs

  • If the CID doesn't point to audio (detected by ffmpeg probe), return 415 Unsupported Media Type
  • If IPFS fetch fails (timeout, gateway down), return 502 Bad Gateway
  • If ffmpeg binary is missing (dev environment), log warning and return 501 with "Preview not available"

Dependencies to add

{
  "fluent-ffmpeg": "^2.1.3",
  "ffmpeg-static": "^5.2.0"
}

Files to create/modify

  • src/routes/ipfs.ts — add GET /:cid/preview route
  • src/services/previewGenerator.ts — new (ffmpeg pipeline)
  • src/services/previewCache.ts — new (LRU cache)
  • src/middleware/rangeParser.ts — new (HTTP Range header parsing)
  • package.json — add fluent-ffmpeg, ffmpeg-static

Acceptance criteria

  • GET /api/ipfs/:cid/preview returns a 30-second MP3 clip
  • Works with MP3, WAV, FLAC, and AIFF source files
  • Preview starts at 30s into the beat (skips intro), configurable via ?start= param
  • Duration is configurable via ?duration= param (max 60s)
  • Response has correct Content-Type: audio/mpeg and Accept-Ranges: bytes
  • Range header is supported for seeking
  • Cached previews return in < 10ms
  • Uncached preview generates in < 5s for a 5MB source file
  • Invalid CID returns 400, non-audio CID returns 415, IPFS failure returns 502
  • Client disconnect aborts ffmpeg processing
  • No system ffmpeg dependency — uses ffmpeg-static

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions