Skip to content

melchor629/node-flac-bindings

Repository files navigation

flac-bindings

Nodejs bindings to libFLAC

Node-API v8 Node CI Coverage Status

How to install

$ npm install flac-bindings

$ yarn add flac-bindings

This is a pure ESM package. If you get an error like require() of ES Module, then read this gist. If you still want to use CJS, then either import using require('flac-bindings'), use dynamic import import('flac-bindings'), or use any of the 2.7.x versions.

The library has some native code that binds the JS code to the flac library. Depending on your platform, an already-compiled library is available for you. The following logic applies:

  • If your CPU is amd64/x86_64 and the OS is Linux/glibc, Linux/musl, macOS or Windows, and node version is in the N-API compatibility table for v8, then the prebuild version will download.
  • If your CPU is arm64/aarch64 and the OS is Linux/glibc, Linux/musl or macOS, and node version is in the aformentioned compatibility table for v8, then the prebuild version will download.
  • If you have pkg-config and libFLAC development package installed (apt install libflac-dev, pacman -S flac, apk add flac-dev, brew install flac...), then it will use this library and only compile the binding code. Requires you to also have Cmake installed.
  • In any other case, it will download libogg and libFLAC source code and compile both libraries plus the binding code. Requires you to also have Cmake and git installed.

See How to compile section for more information.

How to use

For use it, include with

// ESM import (Streams and native API alias)
import {
  api,
  FileDecoder,
  StreamDecoder,
  FileEncoder,
  StreamEncoder,
} from 'flac-bindings';

// ESM import for native API
import {
  Encoder,
  Decoder,
  format,
  metadata,
  metadata0,
  SimpleIterator,
  Chain,
  Iterator,
  fns,
} from 'flac-bindings/api';

Note: this library has its own TypeScript typings, so it's possible to use it in a TS project and have the right types

Note: this library is written in ES Modules, and cannot be imported from CommonJS modules - see this

Examples

Here's an example of using flac-bindings to encode some raw PCM data coming from process.stdin to a FLAC file that gets piped to process.stdout:

import { StreamEncoder } from 'flac-bindings';

// create the Encoder instance
const encoder = new StreamEncoder({
  channels: 2,        // 2 channels (left and right)
  bitsPerSample: 16,  // 16-bit samples
  samplerate: 44100,  // 44,100 Hz sample rate

  compressionLevel: 7,
});

// raw PCM data from stdin gets piped into the encoder
process.stdin.pipe(encoder);

// the generated FLAC file gets piped to stdout
encoder.pipe(process.stdout);

See examples for more examples. See the tests directory for even more examples using advanced API.

How it works

The module is be divided in various sections:

  • StreamEncoder - a stream.Transform class for encoding raw PCM streams
  • FileEncoder - a stream.Writable class for encoding raw PCM streams into a file
  • StreamDecoder - a stream.Transform class for decoding FLAC into a PCM stream
  • FileDecoder - a stream.Readable class for decoding FLAC file into a PCM stream
  • api - the native bindings, easy to use from JS side
    • format - includes only the functions and some types
    • Encoder - the StreamEncoder API
    • Decoder - the StreamDecoder API
    • metadata - includes the StreamMetadata classes and their methods
    • metadata0 - metadata level 0 APIs
    • SimpleIterator - metadata level 1 iterator class
    • Chain - metadata level 2 Chain class
    • Iterator - metadata level 2 Iterator class

The package includes typings that could help you :)

All memory is managed by the library. But there are some methods that give you references to objects that can be destroyed before the JS object. These methods are documented with such special behaviour.

Almost every function/method expects his parameters in his right type (as in the FLAC documentation). If it not, an JS exception will be thrown, or in the worst scenario, a crash (report an issue if this happens). So, pay attention on the types of the functions (the typings are just to help you 😀).

Callbacks don't follow exactly the same signature that shows in Encoder and Decoder sections (from the FLAC documentation). They don't need some of the parameters as in JS there are other ways to get the encoder/decoder instance and some context. The init functions don't receive any private data.

There are asynchronous functions and methods for IO bound tasks. The syncrhonous API will be faster, but will block node. If you are writing an server or expect high concurrency, use the asynchronous API.

You need node version that supports v8 N-API (see compatibility table), which is supported in node v14.17.0/v16.0.0 or higher. Recommended use of BigInt when possible to have numbers be represented without truncation (Number can only store 53 bit integers! 🤨).

Note: Buffers from Encoder, Decoder and IO Callbacks (metadata level 2) have a strict lifetime: buffers are ensured to be valid inside the callback itself, if the buffer must be used outside the callback, make a copy.

Debug the library

When using StreamEncoder, StreamDecoder, FileEncoder or FileDecoder, and something does not work properly, you can enable verbose/debug logs by defining the environment variable DEBUG=flac:* (see debug package for more information). Each class has its own namespace, so you can enable debug logs only for some of them. See below the list of namespaces:

  • StreamEncoder: flac:encoder:stream
  • FileEncoder: flac:encoder:file
  • StreamDecoder: flac:decoder:stream
  • FileDecoder: flac:decoder:file
  • The post-install script: flac:build

!! These logs can be useful when creating a new issue.

You can also try to debug the native code by setting up a test JS file and launching the debugger with the node executable and arguments the script itself.

How to compile

In case you need to compile or want to, ensure to install the optional peer dependencies! These are optional because they are not required in general terms as the pre-compiled code is enought for most cases.

To compile the bindings you need Cmake installed in your system and accessible from the terminal, and the C and C++ compilers as well. On Windows, the compilers can be installed easily with npm install --global --production windows-build-tools. Don't forget git. It is mandatory!

There are some options to use when compiling. The build tries to use an already installed compatible dev package of libflac (apt install libflac-dev, pacman -S flac, apk add flac-dev, brew install flac...), but with FLAC_BINDINGS_USE_FLAC_SOURCES will force to download the sources and compile everything from there.

Supported libFLAC versions are 1.3.x and 1.4.x (binary versions 10 and 12).

Then, you just need to recompile the package with: npm rebuild flac-bindings. If you are inside this repo tree, then run npm run install.

For more advanced commands for compilation inside the repo tree, see below:

# Compile (debug version)
# -p -> If desired, tell cmake to run with parallel jobs (faster)
npx cmake-js build --debug -p 4

# Compile with sanitizers (only available on Linux and macOS)
npx cmake-js configure --CDSANITIZE=ON --debug -p 4
npx cmake-js build --debug -p 4

# Force-compile with FLAC sources (can be combined with sanitizers)
npx cmake-js configure --CDUSE_FLAC_SOURCES --debug -p 4
npx cmake-js build --debug -p 4

# Clean compilation folder
npx cmake-js clean

How to run the tests

With a dev environment, and being able to compile the project, ensure to have installed the flac CLI (apt install flac, pacman -S flac, apk add flac, brew install flac...) and present in the $PATH. It is recommended to have installed the FLAC dev package. Also ensure to have installed Cmake and available in the $PATH.

The recommended steps are:

# Do not run tests with sanitizers enabled, it's tricky to make it work
npx cmake-js configure --debug
npx cmake-js build --debug -p 4
npm test

# To run tests with coverage (requires lcov to be installed)
scripts/coverage.sh rebuild # first time
script/coverage.sh          # next times

Happy npm test runs :)

The API

See the wiki for the documentation.