Initial commit from cloned source

This commit is contained in:
Yongjie Xu
2026-01-13 13:48:55 +08:00
commit 38ac64e178
11 changed files with 246 additions and 0 deletions

3
.eslintrc Normal file
View File

@@ -0,0 +1,3 @@
{
"extends": "standard"
}

8
.gitignore vendored Normal file
View File

@@ -0,0 +1,8 @@
node_modules/
dist/
.DS_Store
yarn-error.log
temp.js
package-lock.json
test.png
test2.png

12
.npmignore Normal file
View File

@@ -0,0 +1,12 @@
node_modules/
yarn-error.log
temp.js
package-lock.json
tsconfig.json
tslint.json
.vscode/
src/
.travis.yml
test.png
test2.png
images/

31
.travis.yml Normal file
View File

@@ -0,0 +1,31 @@
language: node_js
node_js: stable
# Travis-CI Caching
cache:
directories:
- node_modules
yarn: true
# S: Build Lifecycle
install:
- yarn
stages:
- name: deploy
jobs:
include:
- stage: deploy
script:
- npm run build
deploy:
provider: npm
email: ""
api_key: "${NPM_TOKEN}"
skip_cleanup: true
on:
branch: master
branches:
only:
- master

21
License Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 JolyneAnasui
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

21
README.md Normal file
View File

@@ -0,0 +1,21 @@
# picgo-plugin-squoosh
为[PicGo](https://github.com/Molunerfinn/PicGo)开发的插件
* 使用[@squoosh/lib](https://github.com/GoogleChromeLabs/squoosh/tree/dev/libsquoosh)压缩图片,所有处理在本地执行,[详见](https://github.com/GoogleChromeLabs/squoosh#privacy)
* 使用图片md5进行重命名
![](https://raw.githubusercontent.com/JolyneAnasui/picgo-plugin-squoosh/main/images/1.png)
## 配置
* 初次使用前请进行配置
![](https://raw.githubusercontent.com/JolyneAnasui/picgo-plugin-squoosh/main/images/2.png)
* `开`表示压缩相应扩展名的图片,反则反之
* `开`表示使用图片md5进行重命名与压缩相互独立
* 使用squoosh的默认压缩配置若想定制请修改`index.js`里的`DefaultEncodeOptions`

BIN
images/1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

BIN
images/2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

BIN
logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

34
package.json Normal file
View File

@@ -0,0 +1,34 @@
{
"name": "picgo-plugin-squoosh",
"version": "1.1.0",
"description": "使用squoosh压缩图片",
"main": "src/index.js",
"publishConfig": {
"access": "public"
},
"homepage": "https://github.com/JolyneAnasui/picgo-plugin-squoosh",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"patch": "npm version patch && git push origin master && git push origin --tags",
"minor": "npm version minor && git push origin master && git push origin --tags",
"major": "npm version major && git push origin master && git push origin --tags"
},
"keywords": [
"picgo",
"picgo-gui-plugin",
"picgo-plugin"
],
"author": "JolyneAnasui",
"license": "MIT",
"devDependencies": {
"eslint": "^5.0.1",
"eslint-config-standard": "^11.0.0",
"eslint-plugin-import": "^2.13.0",
"eslint-plugin-node": "^6.0.1",
"eslint-plugin-promise": "^3.8.0",
"eslint-plugin-standard": "^3.1.0"
},
"dependencies": {
"@squoosh/lib": "^0.4.0"
}
}

116
src/index.js Normal file
View File

@@ -0,0 +1,116 @@
const { ImagePool, encoders } = require('@squoosh/lib');
const crypto = require('crypto');
const pluginConfig = ctx => {
let userConfig = ctx.getConfig('picgo-plugin-squoosh');
if (!userConfig) {
userConfig = {};
}
return [
{ name: 'md5-rename', type: 'confirm', alias: 'md5-rename' },
{ name: '.jpg', type: 'confirm', alias: 'jpg' },
{ name: '.jpeg', type: 'confirm', alias: 'jpeg' },
{ name: '.png', type: 'confirm', alias: 'png' },
{ name: '.webp', type: 'confirm', alias: 'webp' },
{ name: '.avif', type: 'confirm', alias: 'avif' },
{ name: '.jxl', type: 'confirm', alias: 'jxl' },
{ name: '.wp2', type: 'confirm', alias: 'wp2' }
];
};
// 显式定义编码映射
const DefaultEncodeOptions = {
'.jpg': { webp: {} },
'.jpeg': { webp: {} },
'.png': { webp: {} },
'.webp': { webp: {} },
'.avif': { avif: {} },
'.jxl': { jxl: {} },
'.wp2': { wp2: {} }
};
const handle = async (ctx) => {
ctx.log.info('**** squoosh: 转换逻辑启动 ****');
let t0 = new Date();
const userConfig = ctx.getConfig('picgo-plugin-squoosh') || {};
let imagePool = new ImagePool();
const jobs = ctx.output.map(async (outputi) => {
try {
// 统一小写处理后缀
let ext = outputi.extname.toLowerCase();
// 特殊处理:有些剪贴板图片可能不带点
if (!ext.startsWith('.')) ext = '.' + ext;
// 1. 判断是否需要压缩/转换
if (userConfig[ext] !== false && DefaultEncodeOptions[ext]) {
let t = new Date();
const encodeConfig = DefaultEncodeOptions[ext];
const codecName = Object.keys(encodeConfig)[0]; // 获取目标编码器名,如 'webp'
let b = outputi.buffer;
let ab = b.buffer.slice(b.byteOffset, b.byteOffset + b.byteLength);
let image = imagePool.ingestImage(ab);
ctx.log.info(`正在处理: ${outputi.fileName} (源格式: ${ext} -> 目标: ${codecName})`);
// 2. 核心:执行编码
await image.encode(encodeConfig);
const encodedData = await image.encodedWith[codecName];
if (!encodedData) throw new Error(`${codecName} 编码失败`);
// 3. 替换二进制数据
outputi.buffer = Buffer.from(encodedData.binary);
// 4. 【关键步骤】强制重写文件名和后缀
// 只有目标编码器是 webp 且原格式不是 webp 时才修改
if (codecName === 'webp' && ext !== '.webp') {
const oldName = outputi.fileName;
const oldExt = outputi.extname;
// 修改内部属性PicGo 上传插件主要依赖这两个属性
outputi.extname = '.webp';
// 移除旧后缀并追加新后缀
if (oldName.toLowerCase().endsWith(oldExt.toLowerCase())) {
outputi.fileName = oldName.substring(0, oldName.length - oldExt.length) + '.webp';
} else {
outputi.fileName = oldName + '.webp';
}
ctx.log.success(`格式转换成功: ${oldName} -> ${outputi.fileName}`);
}
}
// 5. MD5 重命名逻辑 (必须在后缀修改后执行)
if (userConfig['md5-rename']) {
let hash = crypto.createHash('md5').update(outputi.buffer).digest('hex');
outputi.fileName = hash + outputi.extname;
ctx.log.info(`MD5 重命名: ${outputi.fileName}`);
}
return outputi;
} catch (err) {
ctx.log.error(`[Squoosh 失败] ${outputi.fileName}: ${err.stack}`);
return outputi;
}
});
ctx.output = await Promise.all(jobs);
await imagePool.close();
ctx.log.info(`**** squoosh 处理结束: ${new Date().getTime() - t0.getTime()}ms ****`);
return ctx;
};
module.exports = (ctx) => {
const register = () => {
ctx.helper.beforeUploadPlugins.register('squoosh', {
handle,
})
}
return {
register,
config: pluginConfig,
}
}