crop-github-images-cli
Version:
> Crop your images and gifs for your GitHub Profile!
227 lines (208 loc) • 6.15 kB
JavaScript
;
const { parse, resolve } = require("path");
const fs = require("fs");
const sharp = require("sharp");
const execa = require("execa");
const gifsicle = require("gifsicle");
const updateNotifier = require("update-notifier");
const meow = require("meow");
const got = require("got");
const cp = require("cp-file");
const trash = require("trash");
const { cloneGistPath, commitAll, push } = require("./git-util");
const CONTAINER_WIDTH = 928;
const CUT_WIDTH = 422;
const CUT_HEIGHT = 100;
const CARD_PADDING_TOP = 37;
const CARD_PADDING_HORIZONTAL = 16;
const CARD_PADDING_BOTTOM = 16;
const CARD_MARGIN_BOTTOM = 16;
const CARD_HEIGHT = CARD_PADDING_TOP + CUT_HEIGHT + CARD_PADDING_BOTTOM;
const Y_OFFSET = CARD_HEIGHT + CARD_MARGIN_BOTTOM;
const MINIMUM_HEIGHT = 3 * CARD_HEIGHT + 2 * CARD_MARGIN_BOTTOM;
const cli = meow(
`
Usage
$ crop-github-images <path>
Examples
$ crop-github-images unicorn.png
`,
{
flags: {
"github-token": {
type: "string",
alias: "t"
}
}
}
);
updateNotifier({ pkg: cli.pkg }).notify();
if (cli.input.length === 0) {
console.error("Specify exactly one path to the image/gif");
process.exit(1);
}
const getXY = index => {
const isLeft = index % 2 === 0;
// There is no margin between cards, instead, they are
// separated by flex's space-between, which is directly
// affected by container width.
const x = isLeft
? CARD_PADDING_HORIZONTAL
: CONTAINER_WIDTH - (CARD_PADDING_HORIZONTAL + CUT_WIDTH);
const indexFromTop = Math.floor(index / 2);
const y = CARD_PADDING_TOP + indexFromTop * Y_OFFSET;
return { x, y };
};
const cropImage = async path => {
try {
const { ext, name } = parse(path);
const image = sharp(path);
const { width, height } = await image.metadata();
const resizeOpts =
width / height >= CONTAINER_WIDTH / MINIMUM_HEIGHT
? { width: CONTAINER_WIDTH, height: MINIMUM_HEIGHT, fit: "outside" }
: { width: CONTAINER_WIDTH };
const resized = image.clone().resize(resizeOpts);
const files = [];
for (let i = 0; i < 6; i++) {
const filename = `${name}.${i}${ext}`;
const { x, y } = getXY(i);
await resized
.clone()
.extract({ left: x, top: y, width: CUT_WIDTH, height: CUT_HEIGHT })
.toFile(filename);
console.log(`Successfully cropped ${filename}.`);
files.push(filename);
}
return files;
} catch (e) {
console.error(e);
}
};
const cropGif = async path => {
try {
const { name } = parse(path);
const { width, height } = await sharp(path).metadata();
const dimension = width / height;
const BASE_DIMENSION = CONTAINER_WIDTH / MINIMUM_HEIGHT;
const isWider = dimension >= BASE_DIMENSION;
const resizeOpts = isWider
? ["--resize", `_x${MINIMUM_HEIGHT}`]
: ["--resize", `${CONTAINER_WIDTH}x_`];
const resized = "resized.gif";
await execa(gifsicle, [...resizeOpts, "-o", resized, path]);
const { width: newWidth, height: newHeight } = await sharp(
resized
).metadata();
// console.log({ newWidth, newHeight });
const offsetX = Math.floor((newWidth - CONTAINER_WIDTH) / 2);
const offsetY = Math.floor((newHeight - MINIMUM_HEIGHT) / 2);
const files = [];
for (let i = 0; i < 6; i++) {
const filename = `${name}.${i}.gif`;
const { x, y } = getXY(i);
const xPos = x + offsetX;
const yPos = y + offsetY;
// console.log({
// xPos,
// yPos,
// CUT_WIDTH,
// CUT_HEIGHT
// });
await execa(gifsicle, [
"--crop",
`${xPos},${yPos}+${CUT_WIDTH}x${CUT_HEIGHT}`,
"-o",
filename,
resized
]);
console.log(`Successfully cropped ${filename}.`);
files.push(filename);
}
await trash(resized);
return files;
} catch (e) {
console.error(e);
}
};
const isGif = path => path.endsWith(".gif");
const createGists = async (files, githubToken) => {
const hashes = [];
try {
await Promise.all(
files.map(
file =>
new Promise(async done => {
const files = {
[file]: {
content: `Placeholder for ${file}.`
}
};
const body = JSON.stringify({
description: `Gist for ${file}. Generated by \`crop-github-images-cli\`.`,
public: true,
files
});
const { body: res } = await got.post("gists", {
baseUrl: "https://api.github.com",
headers: {
Authorization: `token ${githubToken}`
},
body
});
const {
html_url,
id: hash,
owner: { login }
} = JSON.parse(res);
console.log(`Prepared the gist for ${file} in ${html_url}`);
await cloneGistPath(hash);
await cp(file, `${hash}/${file}`);
const gitPath = resolve(`./${hash}/.git`);
await commitAll(gitPath);
fs.writeFileSync(
`${hash}/.netrc`,
`machine github.com\nlogin ${login}\npassword ${githubToken}`
);
await push(gitPath);
hashes.push(hash);
console.log(
`${
isGif(path) ? "GIF" : "Image"
} ${file} has been added to the gist`
);
done();
})
)
);
await trash(hashes);
console.log("Cleaned working directory.");
} catch (e) {
console.error(e);
if (hashes.length) {
await trash(hashes);
}
}
};
const crop = async (path, githubToken) => {
let files = [];
try {
if (isGif(path)) {
files = await cropGif(path);
} else {
files = await cropImage(path);
}
if (githubToken) {
createGists(files, githubToken);
}
} catch (e) {
console.error(e);
if (files.length) {
await trash(files);
}
}
};
const path = cli.input[0];
const { githubToken } = cli.flags;
crop(path, githubToken);