@gltf-transform/cli
Advanced tools
+9
-10
| { | ||
| "name": "@gltf-transform/cli", | ||
| "version": "0.4.0-alpha.3", | ||
| "version": "0.4.0", | ||
| "repository": "github:donmccurdy/glTF-Transform", | ||
@@ -17,11 +17,10 @@ "description": "CLI interface to glTF Transform", | ||
| "dependencies": { | ||
| "@gltf-transform/ao": "^0.4.0-alpha.3", | ||
| "@gltf-transform/colorspace": "^0.4.0-alpha.3", | ||
| "@gltf-transform/core": "^0.4.0-alpha.3", | ||
| "@gltf-transform/extensions": "^0.4.0-alpha.3", | ||
| "@gltf-transform/prune": "^0.4.0-alpha.3", | ||
| "@gltf-transform/split": "^0.4.0-alpha.3", | ||
| "@gltf-transform/ao": "^0.4.0", | ||
| "@gltf-transform/colorspace": "^0.4.0", | ||
| "@gltf-transform/core": "^0.4.0", | ||
| "@gltf-transform/extensions": "^0.4.0", | ||
| "@gltf-transform/prune": "^0.4.0", | ||
| "@gltf-transform/split": "^0.4.0", | ||
| "caporal": "^1.3.0", | ||
| "gl": "^4.5.0", | ||
| "minimatch": "^3.0.4" | ||
| "gl": "^4.5.0" | ||
| }, | ||
@@ -34,3 +33,3 @@ "files": [ | ||
| ], | ||
| "gitHead": "6ab967a22de4bfb41845d8d0cfb9ca743f324adb" | ||
| "gitHead": "9a2c30c6dd0f0222e86e5f65a8c13f07b9765b1c" | ||
| } |
+0
-33
@@ -15,3 +15,2 @@ #!/usr/bin/env node | ||
| const { prune } = require('@gltf-transform/prune'); | ||
| const { toktx, Mode, TOKTX_DEFAULTS } = require('./toktx'); | ||
@@ -104,34 +103,2 @@ const io = new NodeIO(fs, path).registerExtensions(KHRONOS_EXTENSIONS); | ||
| // KTX | ||
| program | ||
| .command('ktx', 'Compresses textures with KTX + Basis Universal') | ||
| .argument('<input>', 'Path to read glTF 2.0 (.glb, .gltf) input') | ||
| .argument('<output>', 'Path to write output') | ||
| .option( | ||
| '--mode <mode>', | ||
| 'Basis Universal compression mode ["etc1s" = low quality, "uastc" = high quality]', | ||
| [Mode.ETC1S, Mode.UASTC], Mode.ETC1S | ||
| ) | ||
| .option( | ||
| '--quality <quality>', | ||
| 'Quality level, where higher levels mean larger files [0 – 1]', | ||
| program.FLOAT, TOKTX_DEFAULTS.quality | ||
| ) | ||
| .option( | ||
| '--zstd <zstd>', | ||
| 'ZSTD compression level (UASTC only) [1 – 20, 0 = Off]', | ||
| program.FLOAT, 0 | ||
| ) | ||
| .option( | ||
| '--slots <slots>', | ||
| 'Texture slots to compress (glob expression)', | ||
| program.STRING, "*" | ||
| ) | ||
| .action(({input, output}, options, logger) => { | ||
| const doc = io.read(input) | ||
| .setLogger(logger) | ||
| .transform(toktx(options)); | ||
| io.write(output, doc); | ||
| }); | ||
| // UNLIT | ||
@@ -138,0 +105,0 @@ program |
-115
| const fs = require('fs'); | ||
| const tmp = require('tmp'); | ||
| const minimatch = require('minimatch'); | ||
| const { spawnSync } = require('child_process'); | ||
| const { BufferUtils, ImageUtils, FileUtils, PropertyType } = require('@gltf-transform/core'); | ||
| const { TextureBasisu } = require('@gltf-transform/extensions'); | ||
| const Mode = { | ||
| ETC1S: 'etc1s', | ||
| UASTC: 'uastc', | ||
| }; | ||
| const TOKTX_DEFAULTS = { | ||
| mode: Mode.ETC1S, | ||
| quality: 0.5, | ||
| zstd: 0, | ||
| slots: '*', | ||
| }; | ||
| tmp.setGracefulCleanup(); | ||
| const toktx = function (options) { | ||
| options = {...TOKTX_DEFAULTS, ...options}; | ||
| return (doc) => { | ||
| const logger = doc.getLogger(); | ||
| doc.createExtension(TextureBasisu); | ||
| doc.getRoot() | ||
| .listTextures() | ||
| .forEach((texture, textureIndex) => { | ||
| const slots = getTextureSlots(doc, texture); | ||
| const textureLabel = texture.getURI() | ||
| || texture.getName() | ||
| || `${textureIndex + 1}/${doc.getRoot().listTextures().length}`; | ||
| logger.debug(`Texture ${textureLabel} (${slots.join(', ')})`); | ||
| // Exclude textures that don't match the 'slots' glob, or are already KTX. | ||
| if (texture.getMimeType() === 'image/ktx2') { | ||
| logger.debug('• Skipping, already KTX.'); | ||
| return; | ||
| } else if (options.slots !== '*' && !slots.find((slot) => minimatch(slot, options.slots, {nocase: true}))) { | ||
| logger.debug(`• Skipping, excluded by pattern "${options.slots}".`) | ||
| return; | ||
| } | ||
| const inExtension = texture.getURI() | ||
| ? FileUtils.extension(texture.getURI()) | ||
| : ImageUtils.mimeTypeToExtension(texture.getMimeType()); | ||
| const inPath = tmp.tmpNameSync({postfix: '.' + inExtension}); | ||
| const outPath = tmp.tmpNameSync({postfix: '.ktx2'}); | ||
| const inBytes = texture.getImage().byteLength; | ||
| fs.writeFileSync(inPath, Buffer.from(texture.getImage())); | ||
| const params = [...createParams(slots, options), outPath, inPath]; | ||
| logger.debug(`• toktx ${params.join(' ')}`); | ||
| // Run `toktx` CLI tool. | ||
| const {status, error} = spawnSync('toktx', params, {stdio: [process.stderr]}); | ||
| if (status !== 0) { | ||
| logger.error('• Texture compression failed.'); | ||
| throw error || new Error('Texture compression failed'); | ||
| } | ||
| texture | ||
| .setImage(BufferUtils.trim(fs.readFileSync(outPath))) | ||
| .setMimeType('image/ktx2'); | ||
| logger.debug(`• ${inBytes} → ${texture.getImage().byteLength} bytes.`); | ||
| }); | ||
| }; | ||
| } | ||
| function getTextureSlots (doc, texture) { | ||
| return doc.getGraph().getLinks() | ||
| .filter((link) => link.getChild() === texture) | ||
| .map((link) => link.getName()) | ||
| .filter((slot) => slot !== 'texture') | ||
| } | ||
| function createParams (slots, options) { | ||
| const params = ['--genmipmap']; | ||
| if (options.mode === Mode.UASTC) { | ||
| params.push('--uastc', Math.round(lerp(0, 4, options.quality))); | ||
| if (options.zstd > 0) params.push('--zcmp', options.zstd); | ||
| } else { | ||
| params.push('--bcmp'); | ||
| params.push('--clevel', Math.round(mlerp(0, 1, 5, options.quality))); | ||
| params.push('--qlevel', Math.round(lerp(1, 255, options.quality))); | ||
| if (slots.find((slot) => minimatch(slot, '*normal*', {nocase: true}))) { | ||
| params.push('--linear', '--normal_map'); | ||
| } | ||
| } | ||
| return params; | ||
| } | ||
| function lerp(v0, v1, t) { | ||
| return v0 * (1 - t) + v1 * t; | ||
| } | ||
| /** lerp given a specified midpoint to use for 0.5. */ | ||
| function mlerp(v0, v1, v2, t) { | ||
| if (t > 0.5) { | ||
| return lerp(v1, v2, (t - 0.5) * 2); | ||
| } | ||
| return lerp(v0, v1, t * 2); | ||
| } | ||
| module.exports = {toktx, Mode, TOKTX_DEFAULTS}; |
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
8
-11.11%2
-33.33%0
-100%7236
-37.88%4
-20%119
-51.03%- Removed
Updated
Updated
Updated
Updated