calibre-node is a lightweight Node.js module that provides ebook conversion capabilities by wrapping the Calibre CLI tools. It offers built-in queuing and threading to efficiently handle multiple conversions with ease. Please note that this module does not handle the actual conversion algorithms; it interfaces with Calibre's CLI utilities to perform conversions.
First, ensure that you have Calibre installed on your system, as this module relies on its conversion tools. You can download Calibre from the official website. Make sure that the ebook-convert command is available in your system's PATH.
To test if Calibre is installed correctly, run the following command in your terminal:
ebook-convert --versionTo install calibre-node, use npm:
npm install calibre-nodeThe package includes a CLI tool to help install Calibre:
npx calibre-node install calibreWhen installing Calibre using the CLI tool, you can specify additional options:
--install_dir=*path/to/install*: The directory where Calibre will be installed. The default is./calibre-bin(root of the project).
Example usage:
npx calibre-node install calibre --install_dir=/custom/path/to/calibreAfter installing both Calibre and calibre-node, you can start converting ebooks in your Node.js application.
const calibre = require('calibre-node');
// Convert an ebook
calibre.convert({
input: './input/book.pdf',
output: './output/book.epub',
delete: false,
silent: true, // this package's console output
verbose: 'low' // calibre conversion output verbosity
}).then(response => {
console.log('Conversion successful:', response);
}).catch(error => {
console.error('Conversion failed:', error);
});In this example, a PDF file at ./input/book.pdf is converted to an EPUB file at ./output/book.epub. Set the delete option to true if you want to remove the input file after conversion, and silent to false if you want verbose logging.
The convert function accepts an object with the following properties:
input(string, required): The path to the input file.output(string, required): The path where the output file will be saved, including the desired extension.delete(boolean, optional): Whether to delete the input file after conversion. Default isfalse.silent(boolean, optional): If set totrue, suppresses calibre-node package's console output. Default istrue.verbose("low" | "med" | "high", optional): Sets the verbosity level of calibre conversion output. Default is"low". Any other value is rejected with a clear error rather than being passed through to Calibre.timeoutMs(number, optional): Maximum time the underlyingebook-convertprocess may run before it is killed withSIGKILL. Default is120000(2 minutes).ebook-convertcan hang indefinitely on malformed input, so this guarantees a conversion always terminates and never permanently occupies a pool slot.
Additional conversion options supported by Calibre can also be included. Refer to the Calibre conversion documentation for a full list of parameters.
The conversion promise resolves with a result object containing:
success(boolean): Indicates whether the conversion was successfulfilePath(string): The full path where the converted file was savedfilename(string): The name of the converted file without extensionextension(string): The file extension of the converted fileerror(string, optional): Error message if the conversion failed
interface ConversionResult {
success: boolean;
filePath: string;
filename: string;
extension: string;
error?: string;
}On failure the promise rejects with a ConversionError. It is a real Error
(so it carries a stack trace) and additionally exposes the success,
outputPath and error properties that earlier versions rejected with, so
existing .catch() handlers keep working unchanged:
class ConversionError extends Error {
success: false;
outputPath: string;
error: string; // same string as `message`
stderr?: string; // ebook-convert's stderr - why Calibre rejected the file
stdout?: string;
code?: number | string | null;
signal?: string | null; // e.g. 'SIGKILL'
killed?: boolean;
timedOut?: boolean; // true when the conversion hung and was killed
queueFull?: boolean; // true when the request was refused as backpressure
}timedOut lets you distinguish "the conversion hung and we killed it" from
"Calibre rejected this file", and a partially-written output file is removed
automatically on any failure.
calibre.convert({ /* ... */ }).catch(err => {
if (err.timedOut) console.error('Conversion hung and was killed');
else if (err.queueFull) console.error('Server busy, retry later');
else console.error('Calibre said:', err.stderr);
});You can control the number of concurrent conversions by setting the thread pool size:
calibre.setPoolSize(2); // Allows two conversions to run simultaneously. Default is 2.Worker threads are created only when a pool slot is free, so setPoolSize is a
hard bound on concurrent threads and memory, not just on concurrent executions.
Conversions exceeding the pool size are queued and processed as threads become
available. The queue is bounded: once maxQueueSize requests are already
waiting, further convert() calls reject immediately with queueFull: true
instead of the queue growing without limit.
calibre.setMaxQueueSize(100); // Default is 100. Pass Infinity for an unbounded queue.
// Introspection, useful for health checks and metrics:
calibre.getPoolSize();
calibre.getMaxQueueSize();
calibre.getActiveCount(); // conversions currently running
calibre.getPendingCount(); // conversions waiting for a slotIf the ebook-convert command is not in your system's PATH, you can specify the full path to the Calibre CLI tools:
calibre.setCalibrePath('/path/to/calibre');This module is open-source under the MIT license. Contributions, issues, and feature requests are welcome! Feel free to fork and submit pull requests.
calibre-node is an improvement over the node-ebook-converter package.