UNPKG

3.88 kBJavaScriptView Raw
1/*!
2 * The MIT License (MIT)
3 *
4 * Copyright (c) 2019 Mark van Seventer
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy of
7 * this software and associated documentation files (the "Software"), to deal in
8 * the Software without restriction, including without limitation the rights to
9 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
10 * the Software, and to permit persons to whom the Software is furnished to do so,
11 * subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in all
14 * copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
18 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
19 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
20 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 */
23
24// Strict mode.
25'use strict'
26
27// Standard lib.
28const fs = require('fs')
29const path = require('path')
30
31// Package modules.
32const bubbleError = require('bubble-stream-error')
33const { globSync } = require('glob')
34const isDirectory = require('is-directory')
35const sharp = require('sharp')
36
37// Local modules.
38const queue = require('./queue')
39
40// Configure.
41const EXTENSIONS = {
42 avif: '.avif',
43 dz: '', // Determined by tile.container.
44 gif: '.gif',
45 heif: '.avif',
46 jpeg: '.jpg',
47 png: '.png',
48 tiff: '.tiff',
49 webp: '.webp'
50}
51
52// Exports.
53module.exports = {
54 // Convert a list of files.
55 files: (input, output, options) => {
56 // Resolve files.
57 const files = input.reduce((list, input) => {
58 return list.concat(globSync(input, { absolute: true }))
59 }, [])
60
61 if (files.length === 0) {
62 return Promise.reject(new Error('No input files'))
63 }
64
65 // Process files.
66 const isBatch = files.length > 1
67 const promises = files.map((src) => {
68 // Create pipeline.
69 const transformer = queue.drain(sharp(options))
70
71 // Process output as a template.
72 const parts = path.parse(src)
73 const regex = /\{(root|dir|base|ext|name)\}/g
74 let dest = output
75 let match
76 while ((match = regex.exec(output)) !== null) {
77 const [search, prop] = match
78 dest = dest.replace(search, parts[prop])
79 }
80 dest = path.resolve(dest)
81
82 // If output was not a template, assume dest is a directory when using
83 // batch processing.
84 const outputAssumeDir = dest === path.resolve(output) && isBatch
85 if (outputAssumeDir || isDirectory.sync(dest)) {
86 const defaultExt = path.extname(src)
87 const desiredExt = transformer.options.formatOut
88 dest = path.format({
89 dir: dest,
90 name: path.basename(src, defaultExt),
91 ext: desiredExt in EXTENSIONS ? EXTENSIONS[desiredExt] : defaultExt
92 })
93 }
94
95 // Write, attach info and return.
96 fs.createReadStream(src).pipe(transformer)
97 return transformer
98 .toFile(dest)
99 .then((info) => Object.assign(info, { src, path: dest }))
100 })
101 return Promise.all(promises)
102 },
103
104 // Convert a stream.
105 stream: (inStream, outStream, options) => {
106 return new Promise((resolve, reject) => {
107 // Create pipeline.
108 const transformer = queue.drain(sharp(options))
109
110 // Gather return value.
111 const info = { }
112 transformer.on('info', (_info) => Object.assign(info, _info))
113
114 // Pipe, and return as promise.
115 bubbleError(inStream, transformer, outStream)
116 inStream.pipe(transformer).pipe(outStream)
117 outStream.once('error', reject)
118 outStream.on('finish', () => resolve(info))
119 })
120 }
121}