diff --git a/rawtherapee.js b/rawtherapee.js new file mode 100644 index 0000000..aa76d68 --- /dev/null +++ b/rawtherapee.js @@ -0,0 +1,175 @@ +const fs = require('fs') +const { spawn } = require('child_process') + +const defaultOptions = { + replace: false, + allFormats: false, + presets: ['default'], + ignoreBadPreset: false, + output: '.', + format: 'jpg', + depth: 8, + compression: 90, + subSampling: 2, + zip: false, + onChange: () => {} +} + +const o2cli = (options) => { + const params = ['-q'] + const { + replace, + allFormats, + presets, + ignoreBadPreset, + output, + format, + compression, + subSampling, + zip, + depth + } = options + if (replace) params.push('-Y') + if (allFormats) params.push('-a') + if (presets || presets.length) { + presets.forEach((preset) => { + switch (preset) { + case 'default': + return params.push('-d') + case 'sidecar': + return params.push('-s') + case 'sidecar-strict': + return params.push('-S') + default: + if (!fs.existsSync(preset)) { + const err = new Error(`Preset '${preset}' not found.`) + if (!ignoreBadPreset) throw err + else if (process.env.DEBUG) console.error(err) + } else { + params.push('-p') + params.push(preset) + } + } + }) + } + if (format) { + switch (format.toLowerCase()) { + case 'jpg': + case 'jpeg': + params.push(`-j${compression || ''}`) + if (subSampling) { + switch (subSampling) { + case '4:2:0': + case 1: + params.push('-js1') + break + case '4:2:2': + case 2: + params.push('-js2') + break + case '4:4:4': + case 3: + params.push('-js3') + break + default: + console.warn( + `Warning: subSampling '${subSampling}' is unknown. Option is ignored, 'rawtherapee-cli' will use his own default (4:2:2).` + ) + } + } + break + case 'png': + params.push('-n') + break + case 'tif': + case 'tiff': + params.push(`-t${zip && 'z'}`) + break + } + } + if (depth) params.push(`-b${depth}`) + if (output) { + params.push('-o') + params.push(output) + } + return params +} + +const rawtherapee = (file, options = {}) => { + options = { ...defaultOptions, ...options } + const { onChange } = options + return new Promise((resolve, reject) => { + let ckf = file + let filename + if (!Array.isArray(file)) ckf = [file] + if ((filename = ckf.find((fl) => !fs.existsSync(fl)))) { + return reject(new Error(`File ${filename} not found`)) + } + const params = o2cli(options) + params.push('-c') + params.push(file) + const rwtp = spawn('rawtherapee-cli', params) + + const files = [] + + rwtp.stdout.on('data', (buf) => { + const data = buf.toString('utf8') + let ipath = /Processing: (.+)$/im.exec(data) + if (ipath) { + if (files.length) { + onChange({ + status: 'complete', + file: files[files.length - 1] + }) + } + files.push(ipath[1]) + onChange({ + status: 'processing', + file: ipath[1] + }) + } + if (!ipath) { + ipath = /^RawTherapee, version ([0-9.]+), command line./im.exec(data) + if (ipath) { + onChange({ + status: 'start', + version: ipath[1] + }) + } + } + if (!ipath) { + ipath = /^"(.+)".*image skipped/im.exec(data) + if (ipath) { + onChange({ + status: 'skipped', + file: ipath[1] + }) + } + } + if (!ipath && process.env.DEBUG) console.warn('not handled', data) + }) + + rwtp.stderr.on('data', (data) => { + console.error(`stderr: ${data}`) + reject(new Error(data)) + }) + rwtp.on('error', (data) => { + console.error(`error: ${data}`) + reject(new Error(data)) + }) + + rwtp.on('close', (code) => { + if (code > 0) return reject(new Error(`rawtherapee-cli process exited with code ${code}`)) + if (files.length) { + onChange({ + status: 'complete', + file: files[files.length - 1] + }) + } + onChange({ status: 'idle', code }) + resolve(files) + }) + }) +} + +module.exports = rawtherapee