Skip to content

Fluent FFmpeg wrapper with real-time audio effects, timeout and progress tracking.

License

Notifications You must be signed in to change notification settings

Dirold2/fluent-streamer

Repository files navigation

Fluent Streamer

Перейти на русскую версию

Fluent FFmpeg wrapper for Node.js, written in TypeScript.
Offers a fluent, chainable API for media/audio/video processing with FFmpeg, supporting streams, crossfade, audio effects, timeouts, and progress tracking.

  • TypeScript-first: typed, chainable, and modern
  • Streaming: easy integration with Node streams
  • Audio processing: built-in EQ, volume, compression controls
  • Human-friendly: good defaults and self-describing API
  • Advanced features: crossfade, format conversion, codec control, kill/timeout, progress

Powered by FFmpeg.
Works in Node.js and supports streaming (no file required).


Installation

# From npm (when published)
npm install fluent-streamer

# From GitHub (latest)
npm install github:dirold2/fluent-streamer

# Using Yarn
yarn add github:dirold2/fluent-streamer

# Using PNPM
pnpm install github:dirold2/fluent-streamer

# Using Bun
bun install github:dirold2/fluent-streamer

Usage

import FluentStream from "fluent-streamer";

// Basic conversion example
const fs = new FluentStream()
  .input("input.wav")
  .audioCodec("aac")
  .audioBitrate("192k")
  .output("output.m4a");

const { done } = fs.run();
done.then(() => console.log("Conversion finished."));

Stream Processing

import FluentStream from "fluent-streamer";
import fs from "node:fs";

const input = fs.createReadStream("track.mp3");
const output = fs.createWriteStream("new.wav");

const f = new FluentStream()
  .input(input)
  .format("wav")
  .output(output);

f.run().done.then(() => {
  console.log("Done streaming!");
});

Crossfade Example

const streamer = new FluentStream();
streamer
  .input("a.mp3")
  .input("b.mp3")
  .crossfadeAudio(2.5)    // 2.5 seconds crossfade
  .output("x.mp3")
  .run();

Advanced Streaming Example

const https = require('https');

// Stream from HTTP source to response
app.get('/audio/:fileId', async (req, res) => {
  const audioUrl = `https://cdn.example.com/audio/${req.params.fileId}.mp3`;

  try {
    const streamer = new FluentStream()
      .input(audioUrl)
      .setHeaders({
        'Authorization': 'Bearer token',
        'X-Client-Id': 'my-app'
      })
      .audioCodec('aac')
      .audioBitrate('128k')
      .format('mp3')
      .output(FluentStream.stdout);

    const { output } = await streamer.run();

    res.setHeader('Content-Type', 'audio/mpeg');
    res.setHeader('Content-Disposition', 'inline; filename="converted.mp3"');

    output.pipe(res);
  } catch (error) {
    console.error('Audio stream error:', error);
    res.status(500).send('Streaming failed');
  }
});

Real-time Audio Effects

// Create streamer for live audio processing
const streamer = new FluentStream({
  enableProgressTracking: true,
  useAudioProcessor: true
});

const inputStream = getAudioSource();
const outputStream = fs.createWriteStream('processed.wav');

const { output, done } = streamer
  .input(inputStream)
  .audioCodec('pcm_s16le')
  .audioFrequency(44100)
  .audioChannels(2)
  .output(outputStream)
  .run();

// Adjust audio effects in real-time
streamer.changeVolume(1.5);  // Boost volume by 50%
streamer.changeBass(5);      // Increase bass
streamer.changeTreble(-3);   // Reduce treble

// Listen for progress events
streamer.on('progress', (progress) => {
  console.log(`Processing: ${progress.progress}%`);
});

await done;

Fade Effects and Transitions

// Fade in/out example
const streamer = new FluentStream()
  .input('background-music.mp3')
  .setVolume(0)  // Start silent
  .output('fade-demo.mp3');

// Fade in over 2 seconds
await new Promise(resolve => setTimeout(resolve, 1000));
streamer.fadeIn(1, 2000);  // Target volume 1, fade time 2000ms

await new Promise(resolve => setTimeout(resolve, 8000));
// Fade out over 3 seconds
streamer.fadeOut(3000);

const { done } = streamer.run();
await done;

Audio Effects Control

// Real-time audio adjustments during playback
const streamer = new FluentStream({
  enableProgressTracking: true,
  useAudioProcessor: true
})
  .input("music.mp3")
  .setVolume(1.5)     // Increase volume by 50%
  .setBass(8)         // Boost bass
  .setTreble(-3)      // Cut treble slightly
  .setCompressor(true) // Enable dynamic compression
  .output("enhanced.wav");

// Adjust effects during playback
await streamer.run().done;

API Highlights

FluentStream provides a fluent interface:

new FluentStream()
  .input("song.mp3")
  .seekInput(30)          // seek to 30s
  .audioCodec("opus")
  .audioBitrate("128k")
  .output("clip.opus")
  .run();

Main methods:

  • .input(src) — add file/stream input
  • .output(dst) — set the output (file/stream/fd)
  • .audioCodec(codec) / .videoCodec(codec) — set codecs
  • .audioBitrate(bps) / .videoBitrate(bps)
  • .format(fmt) — set output format (e.g. 'mp3', 'wav')
  • .seekInput(time) — seek input position
  • .overwrite() — overwrite output file(s)
  • .map(spec) — select specific streams
  • .crossfadeAudio(seconds, options?) — crossfade (audio)
  • .run() — start processing (returns output, done, stop)

Complete API Reference

Constructor

new FluentStream(options?: ProcessorOptions)

Creates a new FluentStream instance with optional processor options.

Options:

  • timeout?: number - FFmpeg process timeout in seconds
  • enableProgressTracking?: boolean - Enable progress event emission
  • failFast?: boolean - Stop processing on first error
  • wallTimeLimit?: number - Maximum wall clock time limit
  • useAudioProcessor?: boolean - Enable built-in audio processing
  • audioProcessorOptions?: AudioProcessingOptions - Audio effect defaults

Input/Output Methods

.input(source: string | Readable, options?: InputOptions)

Add file, URL, or stream input.

// File input
.input('/path/to/audio.mp3')

// HTTP URL with custom headers
.input('https://cdn.com/track.mp3')

// Stream input
.input(fs.createReadStream('input.wav'), { pipeIndex: 0 })

Options:

  • label?: string - Input label for identification
  • pipeIndex?: number - Custom pipe index for streams
  • allowDuplicate?: boolean - Allow duplicate inputs

.output(destination: string | Readable | number | PipeObject)

Set output destination.

// File output (auto-overwrites if .overwrite() called)
.output('/path/to/output.mp4')

// Stream output
.output(fs.createWriteStream('out.wav'))

// Stdout
.output(FluentStream.stdout)

// Pipe
.output({ pipe: 'pipe:1' })

Audio/Video Configuration

.audioCodec(codec: string)

Set audio codec: 'aac', 'mp3', 'opus', 'vorbis', 'pcm_s16le', etc.

.videoCodec(codec: string)

Set video codec: 'h264', 'h265', 'vp9', 'av1', 'mpeg4', etc.

.audioBitrate(bitrate: string)

Set audio bitrate: '128k', '192k', '320k', 'variable', etc.

.videoBitrate(bitrate: string)

Set video bitrate: '1M', '2M', '5M', '10M' for high-quality, etc.

.format(format: string)

Set output container format: 'mp3', 'mp4', 'wav', 'flac', 'webm', etc.

.audioFrequency(frequency: number)

Set sample rate: 44100, 48000, 96000, etc.

.audioChannels(channels: number)

Set channel count: 1 (mono), 2 (stereo), 6 (5.1), 8 (7.1), etc.

Processing Controls

.seekInput(position: number | string)

Seek to position in input: 30, '00:00:30', '1:30', etc.

.duration(time: number | string)

Limit output duration.

.map(spec: string)

Select specific streams (advanced FFmpeg feature):

.map('0:v')  // Select first video stream
.map('1:a')  // Select second audio stream
.map('0')    // Select all streams from first input

.noVideo(), .noAudio()

Disable video/audio processing entirely.

.overwrite()

Allow overwriting existing output files.

.copyCodecs()

Copy streams without re-encoding (faster, preserves quality).

Audio Effects (Real-time capable)

.setVolume(value: number)

Set volume multiplier (0-2): 0.5 (half), 1.0 (normal), 1.5 (50% boost).

.setBass(value: number)

Adjust bass level (-20 to 20): 0 (neutral), 5 (boost), -3 (cut).

.setTreble(value: number)

Adjust treble level (-20 to 20): 0 (neutral), 5 (boost), -3 (cut).

.setCompressor(enabled: boolean)

Enable/disable audio compression for consistency.

.setEqualizer(bass, treble, compressor)

Set all EQ parameters at once.

.fadeIn(targetVolume?: number, durationMs?: number)

Fade in from current volume to target volume.

.fadeOut(durationMs?: number)

Fade out to silence.

Real-time changes (during playback):

  • .changeVolume(value: number) — boolean success
  • .changeBass(value: number) — boolean success
  • .changeTreble(value: number) — boolean success
  • .changeCompressor(enabled: boolean) — boolean success
  • .changeEqualizer(bass, treble, compressor) — boolean success

Advanced Features

.crossfadeAudio(duration: number, options?)

Create crossfade between inputs using FFmpeg's acrossfade filter.

.crossfadeAudio(2.5, {
  curve1: 'tri',     // crossfade curve
  curve2: 'tri',
  overlap: true,
  secondInput: 'path/to/second.mp3'
})

.complexFilter(graph: string | string[])

Add complex FFmpeg filter graphs for advanced processing.

Options & Configuration

.setHeaders(headers: Record<string, string>)

Set custom HTTP headers for remote sources.

.userAgent(userAgent: string)

Set User-Agent header for HTTP requests.

.globalOptions(...args), .inputOptions(...args), .outputOptions(...args)

Add FFmpeg arguments at specific positions.

Runtime Control

.run(options?) → Promise<FFmpegRunResult>

Start processing. Returns:

{
  output: Readable,     // Output stream if piped
  done: Promise<void>,  // Completion promise
  stop: () => void      // Stop function
}

.clear()

Reset instance for reuse (required before .run() after previous use).

.isDirtyState(), .isReady()

Check instance state.

Instance Events

  • 'progress' — Progress updates (if enableProgressTracking: true)
  • 'error' — Processing errors
  • 'complete' — Processing finished
  • 'start' — Processing started

Static Methods

Static Pipes

  • FluentStream.stdout — { pipe: 'stdout' }
  • FluentStream.stderr — { pipe: 'stderr' }
  • FluentStream.pipe1 — { pipe: 'pipe:1' }
  • FluentStream.pipe2 — { pipe: 'pipe:2' }

Advanced

  • Timeout: Set the timeout option to auto-kill long FFmpeg jobs.
  • Progress: Get real-time progress events by enabling .options({ enableProgressTracking: true }) and listening for "progress" events.
  • Headers: Default humanity headers are sent to FFmpeg HTTP(S) sources; override with .setHeaders(obj).
  • Kill: run() returns a stop function to terminate process safely.

Types & Extensibility

  • Written in TypeScript: All primary objects (FluentStream, Processor) are strongly typed
  • Built-in Audio processing: Volume, EQ, compression effects through AudioProcessor class
  • Extensible architecture: Built-in audio filters with real-time capabilities

Troubleshooting

Common Issues

FFmpeg not found:

Error: spawn ffmpeg ENOENT
  • Ensure FFmpeg is installed and accessible in PATH
  • On Linux/macOS: which ffmpeg
  • On Windows: Check if ffmpeg.exe is in your PATH
  • Alternative: Provide full path with new FluentStream({ ffmpegPath: '/usr/bin/ffmpeg' })

Stream errors with HTTP sources:

[tcp @ 0x...] Connection refused
  • Verify URL is accessible: curl -I https://example.com/audio.mp3
  • Check network connectivity and firewall settings
  • Some servers block requests without proper headers

Codec/format not supported:

Unknown encoder 'xxx' or unsupported codec
  • Verify FFmpeg installation supports the codec: ffmpeg -codecs | grep xxx
  • Some codecs require additional FFmpeg builds (e.g., non-free codecs)
  • Try simpler codecs like aac for audio, h264 for video

Out of memory during processing:

Cannot allocate memory
  • Reduce input resolution/frame rate for videos
  • Use copyCodecs() for passthrough where quality loss is acceptable
  • Process in smaller chunks for large files
  • Increase system memory limits if possible

File locking/permissions issues:

Permission denied (file locked)
  • Ensure output directory is writable
  • Close file handles before processing
  • On Windows, ensure files aren't open in other applications
  • Use unique temporary filenames

Debugging Tips

Enable verbose logging:

const streamer = new FluentStream({
  logger: console // Enable console logging
});
// Or
import debug from 'debug';
FluentStream.logger = { /* custom logger */ };

Inspect FFmpeg command:

const streamer = new FluentStream()
  .input('in.mp3')
  .audioCodec('aac')
  .output('out.m4a');

console.log('FFmpeg args:', streamer.getArgs());
console.log('Inputs:', streamer.getInputSummary());

Check instance state:

console.log('Is dirty:', streamer.isDirtyState());
console.log('Is ready:', streamer.isReady());
console.log('Debug info:', streamer.debugInfo());

Performance Optimization

For real-time streaming:

  • Use high priority for FFmpeg processes
  • Optimize audio-only processing with .noVideo()
  • Use efficient codecs like aac instead of mp3 for streaming
  • Implement buffering to prevent stutter
  • Monitor system resources during peak usage

For file processing:

  • Use multiple CPU cores with FFmpeg threading: .outputOptions('-threads', '0')
  • Process related tasks in parallel with separate FluentStream instances
  • Cache processed results when possible
  • Use faster presets: .outputOptions('-preset', 'fast')

Memory management:

  • Use streams for large files instead of loading entire content
  • Clean up instances with .clear() after use
  • Limit concurrent FFmpeg processes
  • Monitor memory usage in production

Compatibility

Node.js versions: 18+ recommended FFmpeg versions: 4.0+ required, 5.0+ recommended OS support: Linux, macOS, Windows, Docker containers

Known limitations:

  • Real-time effects work best with PCM audio streams
  • Some advanced filters may not support all input combinations
  • HTTP streaming may have buffering delays depending on network
  • Large crossfades can be memory-intensive

Contributing

Development Setup

git clone https://github.com/Dirold2/fluent-streamer.git
cd fluent-streamer
npm install
npm run build
npm test

Code Style

  • TypeScript: Strict typing required, ESLint compliant
  • Naming: CamelCase for classes, camelCase for methods/properties
  • Documentation: JSDoc comments for all public APIs
  • Tests: Vitest-based, aim for >90% coverage

Adding Features

  1. Create issue on GitHub for new features
  2. Implement in appropriate module (Core, Types, etc.)
  3. Add tests for new functionality
  4. Update documentation (README, JSDoc)
  5. Ensure no breaking changes without major version bump

Audio Processing Extensions

The AudioProcessor class provides built-in audio effects that can be extended:

  • Real-time effects: Volume, EQ, compression available during playback
  • Custom AudioProcessor: Extend AudioProcessor class for new effects
  • Integration: Effects are automatically applied to PCM audio streams
  • Performance: C++ bindings ensure low-latency processing

Reporting Issues

When reporting bugs, please include:

  • Node.js and FFmpeg versions
  • OS and architecture
  • Minimal reproducible example
  • Expected vs actual behavior
  • Debug output if available

Features Roadmap

  • Enhanced video processing filters
  • WebAssembly audio processing for browsers
  • GPU acceleration for video encoding
  • Advanced audio analysis features
  • Streaming server integrations

License

MIT © dirold2


About

Fluent FFmpeg wrapper with real-time audio effects, timeout and progress tracking.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published