mirror of
https://github.com/SrIzan10/makesweet-api.git
synced 2026-05-01 10:55:14 +00:00
176
index.js
176
index.js
@@ -5,46 +5,176 @@ const childProcess = require('child_process')
|
||||
const multer = require('multer')
|
||||
const uuid = require('uuid')
|
||||
const fs = require ('fs')
|
||||
const app = express()
|
||||
const os = require('os')
|
||||
const path = require('path')
|
||||
|
||||
const workDir = '/tmp/api'
|
||||
const port = 3000
|
||||
|
||||
const workDir = '/tmp/makesweet-api'
|
||||
const upload = multer({ dest: workDir })
|
||||
const app = express()
|
||||
|
||||
app.get('/', (req, res) => {
|
||||
res.send('Hello World!')
|
||||
})
|
||||
|
||||
app.post('/make/:template', upload.any('images'), (req, res) => {
|
||||
app.post('/make/:template', upload.any('images'), handleErrors, (req, res) => {
|
||||
if (!req.files) {
|
||||
return res.status(400).json({error: "i hunger for images..."})
|
||||
throw new ApiError(400, 'i hunger for images...');
|
||||
}
|
||||
const template = `templates/${req.params.template}.zip`
|
||||
if (!fs.existsSync(template)) {
|
||||
return res.status(404).json({error: "i seek the template everywhere, but it is not to be found. try 'heart-locket'"})
|
||||
}
|
||||
const path=`${workDir}/${uuid.v4()}.gif`
|
||||
const textHtml = `${process.cwd()}/text.html`;
|
||||
const output = `${workDir}/${uuid.v4()}.gif`;
|
||||
const template = getTemplatePathFromName(req.params.template);
|
||||
const generator = new Generator();
|
||||
generator.useTemplate(template);
|
||||
try {
|
||||
const cwd = process.cwd()
|
||||
const command = ['run', '-v', `${workDir}:${workDir}`, '-v', `${cwd}:/share`, 'paulfitz/makesweet', '--zip', template, '--in']
|
||||
for (const file of req.files) {
|
||||
command.push(file.path)
|
||||
}
|
||||
command.push('--gif', path)
|
||||
console.log(command)
|
||||
|
||||
const result = childProcess.execFileSync('docker', command).stdout
|
||||
res.send(fs.readFileSync(path))
|
||||
generator.addTexts(req.query.text, textHtml);
|
||||
generator.addImages(req.files);
|
||||
generator.setOutput(output);
|
||||
res.send(generator.apply());
|
||||
} finally {
|
||||
for (const file of req.files) {
|
||||
fs.unlinkSync(file.path)
|
||||
}
|
||||
fs.unlinkSync(path)
|
||||
generator.clean();
|
||||
}
|
||||
})
|
||||
|
||||
app.use(handleErrors)
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(`Example app listening at http://localhost:${port}`)
|
||||
})
|
||||
|
||||
function execSync(command) {
|
||||
console.log("Executing: " + command);
|
||||
childProcess.execSync(command, { stdio: 'inherit' });
|
||||
}
|
||||
|
||||
function text2image(htmlTemplate, outputDir, prefix, txt, border) {
|
||||
console.log("text2image:", txt);
|
||||
const data = {
|
||||
text: txt.replace(/[ \n\r\t]+\/\/[ \n\r\t]+/g, '\n').trim()
|
||||
};
|
||||
fs.writeFileSync(`${outputDir}/text.json`, JSON.stringify(data));
|
||||
fs.copyFileSync(htmlTemplate, `${outputDir}/text.html`);
|
||||
console.log("ready");
|
||||
execSync(`wkhtmltoimage --enable-local-file-access --transparent --width 3000 --window-status ready_to_print ${outputDir}/text.html ${outputDir}/${prefix}.png`);
|
||||
console.log("123");
|
||||
execSync(`convert ${outputDir}/${prefix}.png -trim -bordercolor none -border ` + border + ` ${outputDir}/${prefix}2.png`);
|
||||
console.log("234");
|
||||
return `${outputDir}/${prefix}2.png`;
|
||||
}
|
||||
|
||||
function getTemplatePathFromName(name) {
|
||||
if (!name) {
|
||||
throw new ApiError(400, 'i must have a template i cannot do anything without a template');
|
||||
}
|
||||
const nameStr = String(name);
|
||||
if (!nameStr.match(/^[-a-zA-Z0-9]+$/)) {
|
||||
throw new ApiError(400, 'is that really a template: ' + nameStr);
|
||||
}
|
||||
const template = `templates/${nameStr}.zip`
|
||||
if (!fs.existsSync(template)) {
|
||||
throw new ApiError(404, 'i seek the template everywhere, but it is not to be found.');
|
||||
}
|
||||
return template;
|
||||
}
|
||||
|
||||
function handleErrors(err, req, res, next) {
|
||||
console.error(err);
|
||||
if (err.code) {
|
||||
return res.status(err.code).json({error: err.message})
|
||||
} else {
|
||||
res.status(500).json({error: err})
|
||||
}
|
||||
}
|
||||
|
||||
class ApiError extends Error {
|
||||
constructor(code, message) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
}
|
||||
}
|
||||
|
||||
class Generator {
|
||||
constructor() {
|
||||
this.fnames = [];
|
||||
this.images = [];
|
||||
this.texts = [];
|
||||
this.template = null;
|
||||
this.output = null;
|
||||
this.tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'makesweet-api'));
|
||||
}
|
||||
useFile(fname) {
|
||||
this.fnames.push(fname);
|
||||
}
|
||||
useTemplate(fname) {
|
||||
this.useFile(fname);
|
||||
this.template = fname;
|
||||
}
|
||||
addImages(files) {
|
||||
if (!files) { return; }
|
||||
for (const file of files) {
|
||||
this.images.push(file.path);
|
||||
this.fnames.push(file.path);
|
||||
}
|
||||
}
|
||||
addTexts(textOrTexts, htmlTemplate) {
|
||||
console.log({textOrTexts, htmlTemplate});
|
||||
const texts = !textOrTexts ? [] :
|
||||
(typeof textOrTexts === 'string') ? [textOrTexts] :
|
||||
[...textOrTexts];
|
||||
console.log("texts", texts)
|
||||
let i = 0;
|
||||
for (const txt of texts) {
|
||||
const fname = text2image(htmlTemplate, this.tmpDir, `text_${i}`, txt, 60);
|
||||
this.texts.push(fname);
|
||||
this.fnames.push(fname);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
setOutput(output) {
|
||||
this.output = output;
|
||||
this.fnames.push(output);
|
||||
}
|
||||
getVolumes() {
|
||||
const dirs = new Set(this.fnames.map(fname => path.resolve(path.dirname(fname))));
|
||||
return [...dirs].sort();
|
||||
}
|
||||
getCommand() {
|
||||
const command = ['run'];
|
||||
for (const volume of this.getVolumes()) {
|
||||
command.push('-v', `${volume}:${volume}`);
|
||||
}
|
||||
command.push('--mount', 'type=tmpfs,destination=/share')
|
||||
|
||||
command.push('paulfitz/makesweet');
|
||||
command.push('--zip', path.resolve(this.template));
|
||||
command.push('--in');
|
||||
for (const image of this.images) {
|
||||
command.push(image);
|
||||
}
|
||||
for (const text of this.texts) {
|
||||
command.push(text);
|
||||
}
|
||||
command.push('--gif', this.output);
|
||||
return command;
|
||||
}
|
||||
apply() {
|
||||
const command = this.getCommand();
|
||||
console.log("command:", command);
|
||||
childProcess.execFileSync('docker', command);
|
||||
return fs.readFileSync(this.output);
|
||||
}
|
||||
clean() {
|
||||
try {
|
||||
fs.rmSync(this.tmpDir, { recursive: true });
|
||||
for (const image of this.images) {
|
||||
fs.unlinkSync(image);
|
||||
}
|
||||
if (this.output) {
|
||||
fs.unlinkSync(this.output);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`Error removing tmpDir: ${e}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
41
text.html
Normal file
41
text.html
Normal file
@@ -0,0 +1,41 @@
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
body {
|
||||
font-size: 240px;
|
||||
}
|
||||
#target {
|
||||
text-align: center;
|
||||
white-space: pre-line;
|
||||
text-shadow: 0px 0px 8px #fff, 0px 0px 8px #fff, 0px 0px 8px #fff;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<p id="target">
|
||||
This is a test<br>my friend.
|
||||
</p>
|
||||
|
||||
<script>
|
||||
function callback(txt) {
|
||||
document.getElementById("target").innerHTML = JSON.parse(txt).text;
|
||||
window.status = "ready_to_print";
|
||||
}
|
||||
function loadJSON() {
|
||||
|
||||
var xobj = new XMLHttpRequest();
|
||||
xobj.overrideMimeType("application/json");
|
||||
xobj.open('GET', './text.json', true);
|
||||
xobj.onreadystatechange = function () {
|
||||
if (xobj.readyState == 4) {
|
||||
// Required use of an anonymous callback as .open will NOT return a value but simply returns undefined in asynchronous mode
|
||||
callback(xobj.responseText);
|
||||
}
|
||||
};
|
||||
xobj.send(null);
|
||||
}
|
||||
loadJSON();
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user