UNPKG

crop-github-images-cli

Version:

> Crop your images and gifs for your GitHub Profile!

227 lines (208 loc) 6.15 kB
#!/usr/bin/env node "use strict"; 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);