mediaguru 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -0
- package/CODE_OF_CONDUCT.md +41 -0
- package/CONTRIBUTING.md +50 -0
- package/LICENSE +21 -0
- package/README.md +193 -0
- package/RELEASE.md +38 -0
- package/SECURITY.md +24 -0
- package/bin/mediaguru.js +2 -0
- package/dist/cli/interactive.d.ts +1 -0
- package/dist/cli/interactive.js +647 -0
- package/dist/core/batch/index.d.ts +16 -0
- package/dist/core/batch/index.js +66 -0
- package/dist/core/compress/index.d.ts +17 -0
- package/dist/core/compress/index.js +96 -0
- package/dist/core/config/index.d.ts +14 -0
- package/dist/core/config/index.js +56 -0
- package/dist/core/export/index.d.ts +12 -0
- package/dist/core/export/index.js +82 -0
- package/dist/core/image/index.d.ts +44 -0
- package/dist/core/image/index.js +206 -0
- package/dist/core/ocr/index.d.ts +14 -0
- package/dist/core/ocr/index.js +53 -0
- package/dist/core/pdf/index.d.ts +34 -0
- package/dist/core/pdf/index.js +121 -0
- package/dist/core/qr/index.d.ts +12 -0
- package/dist/core/qr/index.js +37 -0
- package/dist/core/screenshot/index.d.ts +12 -0
- package/dist/core/screenshot/index.js +46 -0
- package/dist/core/server/index.d.ts +5 -0
- package/dist/core/server/index.js +101 -0
- package/dist/core/text2img/index.d.ts +14 -0
- package/dist/core/text2img/index.js +64 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +429 -0
- package/dist/plugins/index.d.ts +41 -0
- package/dist/plugins/index.js +61 -0
- package/dist/tests/test.d.ts +1 -0
- package/dist/tests/test.js +108 -0
- package/dist/tests/test_playwright.d.ts +1 -0
- package/dist/tests/test_playwright.js +60 -0
- package/dist/utils/branding.d.ts +6 -0
- package/dist/utils/branding.js +21 -0
- package/dist/utils/file.d.ts +7 -0
- package/dist/utils/file.js +29 -0
- package/dist/utils/templates.d.ts +9 -0
- package/dist/utils/templates.js +347 -0
- package/mediaguru-1.0.0.tgz +0 -0
- package/package.json +51 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export interface PdfEngineOptions {
|
|
2
|
+
outputDir?: string;
|
|
3
|
+
outputPath?: string;
|
|
4
|
+
}
|
|
5
|
+
export declare class PdfEngine {
|
|
6
|
+
/**
|
|
7
|
+
* Compiles Markdown text/file to PDF using Playwright
|
|
8
|
+
*/
|
|
9
|
+
static markdownToPdf(mdPath: string, opts?: PdfEngineOptions): Promise<string>;
|
|
10
|
+
/**
|
|
11
|
+
* Compiles HTML file to PDF using Playwright
|
|
12
|
+
*/
|
|
13
|
+
static htmlToPdf(htmlPath: string, opts?: PdfEngineOptions): Promise<string>;
|
|
14
|
+
/**
|
|
15
|
+
* Helper to render HTML to PDF via Playwright
|
|
16
|
+
*/
|
|
17
|
+
private static htmlStringToPdf;
|
|
18
|
+
/**
|
|
19
|
+
* Merges multiple PDF files into a single PDF
|
|
20
|
+
*/
|
|
21
|
+
static merge(pdfPaths: string[], opts?: PdfEngineOptions): Promise<string>;
|
|
22
|
+
/**
|
|
23
|
+
* Splits a single PDF into individual pages
|
|
24
|
+
*/
|
|
25
|
+
static split(pdfPath: string, opts?: PdfEngineOptions): Promise<string[]>;
|
|
26
|
+
/**
|
|
27
|
+
* Extracts text from a PDF file using pdf-parse
|
|
28
|
+
*/
|
|
29
|
+
static extractText(pdfPath: string): Promise<string>;
|
|
30
|
+
/**
|
|
31
|
+
* Compresses PDF using pdf-lib optimize options
|
|
32
|
+
*/
|
|
33
|
+
static compress(pdfPath: string, opts?: PdfEngineOptions): Promise<string>;
|
|
34
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { PDFDocument } from 'pdf-lib';
|
|
4
|
+
// @ts-ignore
|
|
5
|
+
import pdfParse from 'pdf-parse';
|
|
6
|
+
import { chromium } from 'playwright';
|
|
7
|
+
import { marked } from 'marked';
|
|
8
|
+
import { getHtmlForPdf } from '../../utils/templates.js';
|
|
9
|
+
import { resolveOutputPath } from '../../utils/file.js';
|
|
10
|
+
export class PdfEngine {
|
|
11
|
+
/**
|
|
12
|
+
* Compiles Markdown text/file to PDF using Playwright
|
|
13
|
+
*/
|
|
14
|
+
static async markdownToPdf(mdPath, opts = {}) {
|
|
15
|
+
const mdContent = fs.readFileSync(mdPath, 'utf-8');
|
|
16
|
+
const htmlBody = await marked.parse(mdContent);
|
|
17
|
+
const fullHtml = getHtmlForPdf(htmlBody);
|
|
18
|
+
const outDir = opts.outputDir || './output';
|
|
19
|
+
const targetPath = opts.outputPath || resolveOutputPath(mdPath, outDir, 'pdf');
|
|
20
|
+
await this.htmlStringToPdf(fullHtml, targetPath);
|
|
21
|
+
return targetPath;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Compiles HTML file to PDF using Playwright
|
|
25
|
+
*/
|
|
26
|
+
static async htmlToPdf(htmlPath, opts = {}) {
|
|
27
|
+
const htmlContent = fs.readFileSync(htmlPath, 'utf-8');
|
|
28
|
+
const outDir = opts.outputDir || './output';
|
|
29
|
+
const targetPath = opts.outputPath || resolveOutputPath(htmlPath, outDir, 'pdf');
|
|
30
|
+
await this.htmlStringToPdf(htmlContent, targetPath);
|
|
31
|
+
return targetPath;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Helper to render HTML to PDF via Playwright
|
|
35
|
+
*/
|
|
36
|
+
static async htmlStringToPdf(html, outputPath) {
|
|
37
|
+
const browser = await chromium.launch({ headless: true });
|
|
38
|
+
try {
|
|
39
|
+
const page = await browser.newPage();
|
|
40
|
+
await page.setContent(html, { waitUntil: 'load' });
|
|
41
|
+
await page.pdf({
|
|
42
|
+
path: outputPath,
|
|
43
|
+
format: 'A4',
|
|
44
|
+
margin: {
|
|
45
|
+
top: '20mm',
|
|
46
|
+
right: '20mm',
|
|
47
|
+
bottom: '20mm',
|
|
48
|
+
left: '20mm',
|
|
49
|
+
},
|
|
50
|
+
printBackground: true,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
finally {
|
|
54
|
+
await browser.close();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Merges multiple PDF files into a single PDF
|
|
59
|
+
*/
|
|
60
|
+
static async merge(pdfPaths, opts = {}) {
|
|
61
|
+
if (pdfPaths.length < 2) {
|
|
62
|
+
throw new Error('At least 2 PDF files are required for merging.');
|
|
63
|
+
}
|
|
64
|
+
const mergedDoc = await PDFDocument.create();
|
|
65
|
+
for (const pdfPath of pdfPaths) {
|
|
66
|
+
const pdfBytes = fs.readFileSync(pdfPath);
|
|
67
|
+
const doc = await PDFDocument.load(pdfBytes);
|
|
68
|
+
const copiedPages = await mergedDoc.copyPages(doc, doc.getPageIndices());
|
|
69
|
+
copiedPages.forEach((page) => mergedDoc.addPage(page));
|
|
70
|
+
}
|
|
71
|
+
const mergedBytes = await mergedDoc.save();
|
|
72
|
+
const outDir = opts.outputDir || './output';
|
|
73
|
+
const targetPath = opts.outputPath || path.join(outDir, 'merged.pdf');
|
|
74
|
+
fs.writeFileSync(targetPath, mergedBytes);
|
|
75
|
+
return targetPath;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Splits a single PDF into individual pages
|
|
79
|
+
*/
|
|
80
|
+
static async split(pdfPath, opts = {}) {
|
|
81
|
+
const pdfBytes = fs.readFileSync(pdfPath);
|
|
82
|
+
const mainDoc = await PDFDocument.load(pdfBytes);
|
|
83
|
+
const totalPages = mainDoc.getPageCount();
|
|
84
|
+
const createdPaths = [];
|
|
85
|
+
const outDir = opts.outputDir || './output';
|
|
86
|
+
const baseName = path.basename(pdfPath, path.extname(pdfPath));
|
|
87
|
+
for (let i = 0; i < totalPages; i++) {
|
|
88
|
+
const pageDoc = await PDFDocument.create();
|
|
89
|
+
const [copiedPage] = await pageDoc.copyPages(mainDoc, [i]);
|
|
90
|
+
pageDoc.addPage(copiedPage);
|
|
91
|
+
const pageBytes = await pageDoc.save();
|
|
92
|
+
const pagePath = path.join(outDir, `${baseName}_page_${i + 1}.pdf`);
|
|
93
|
+
fs.writeFileSync(pagePath, pageBytes);
|
|
94
|
+
createdPaths.push(pagePath);
|
|
95
|
+
}
|
|
96
|
+
return createdPaths;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Extracts text from a PDF file using pdf-parse
|
|
100
|
+
*/
|
|
101
|
+
static async extractText(pdfPath) {
|
|
102
|
+
const pdfBuffer = fs.readFileSync(pdfPath);
|
|
103
|
+
const data = await pdfParse(pdfBuffer);
|
|
104
|
+
return data.text;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Compresses PDF using pdf-lib optimize options
|
|
108
|
+
*/
|
|
109
|
+
static async compress(pdfPath, opts = {}) {
|
|
110
|
+
const pdfBytes = fs.readFileSync(pdfPath);
|
|
111
|
+
const doc = await PDFDocument.load(pdfBytes);
|
|
112
|
+
// Save with compression & minification
|
|
113
|
+
const compressedBytes = await doc.save({
|
|
114
|
+
useObjectStreams: true,
|
|
115
|
+
});
|
|
116
|
+
const outDir = opts.outputDir || './output';
|
|
117
|
+
const targetPath = opts.outputPath || resolveOutputPath(pdfPath, outDir, 'pdf', '_compressed');
|
|
118
|
+
fs.writeFileSync(targetPath, compressedBytes);
|
|
119
|
+
return targetPath;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface QrEngineOptions {
|
|
2
|
+
outputDir?: string;
|
|
3
|
+
outputPath?: string;
|
|
4
|
+
svg?: boolean;
|
|
5
|
+
size?: number;
|
|
6
|
+
}
|
|
7
|
+
export declare class QrEngine {
|
|
8
|
+
/**
|
|
9
|
+
* Generates a QR Code image or vector file from text/url
|
|
10
|
+
*/
|
|
11
|
+
static generate(text: string, opts?: QrEngineOptions): Promise<string>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import QRCode from 'qrcode';
|
|
4
|
+
import { ConfigManager } from '../config/index.js';
|
|
5
|
+
export class QrEngine {
|
|
6
|
+
/**
|
|
7
|
+
* Generates a QR Code image or vector file from text/url
|
|
8
|
+
*/
|
|
9
|
+
static async generate(text, opts = {}) {
|
|
10
|
+
const outDir = opts.outputDir || ConfigManager.getOutputFolder();
|
|
11
|
+
const isSvg = opts.svg || false;
|
|
12
|
+
const size = opts.size || 300;
|
|
13
|
+
const ext = isSvg ? 'svg' : 'png';
|
|
14
|
+
const targetPath = opts.outputPath || path.join(outDir, `qr.${ext}`);
|
|
15
|
+
// Ensure output dir exists
|
|
16
|
+
if (!fs.existsSync(outDir)) {
|
|
17
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
18
|
+
}
|
|
19
|
+
if (isSvg) {
|
|
20
|
+
// Generate SVG as a string and write to file
|
|
21
|
+
const svgString = await QRCode.toString(text, {
|
|
22
|
+
type: 'svg',
|
|
23
|
+
width: size,
|
|
24
|
+
margin: 2,
|
|
25
|
+
});
|
|
26
|
+
fs.writeFileSync(targetPath, svgString, 'utf-8');
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
// Generate PNG binary directly to file
|
|
30
|
+
await QRCode.toFile(targetPath, text, {
|
|
31
|
+
width: size,
|
|
32
|
+
margin: 2,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
return targetPath;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface ScreenshotOptions {
|
|
2
|
+
outputDir?: string;
|
|
3
|
+
outputPath?: string;
|
|
4
|
+
fullPage?: boolean;
|
|
5
|
+
mobile?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export declare class ScreenshotEngine {
|
|
8
|
+
/**
|
|
9
|
+
* Capture a screenshot of a website using Playwright
|
|
10
|
+
*/
|
|
11
|
+
static capture(url: string, opts?: ScreenshotOptions): Promise<string>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { chromium, devices } from 'playwright';
|
|
4
|
+
import { ConfigManager } from '../config/index.js';
|
|
5
|
+
export class ScreenshotEngine {
|
|
6
|
+
/**
|
|
7
|
+
* Capture a screenshot of a website using Playwright
|
|
8
|
+
*/
|
|
9
|
+
static async capture(url, opts = {}) {
|
|
10
|
+
const outDir = opts.outputDir || ConfigManager.getOutputFolder();
|
|
11
|
+
const config = ConfigManager.load();
|
|
12
|
+
// Parse resolution from config (e.g. "1280x720")
|
|
13
|
+
let width = 1280;
|
|
14
|
+
let height = 720;
|
|
15
|
+
if (config.screenshotResolution && config.screenshotResolution.includes('x')) {
|
|
16
|
+
const parts = config.screenshotResolution.split('x');
|
|
17
|
+
width = parseInt(parts[0], 10) || 1280;
|
|
18
|
+
height = parseInt(parts[1], 10) || 720;
|
|
19
|
+
}
|
|
20
|
+
const domainName = new URL(url).hostname.replace('www.', '');
|
|
21
|
+
const filename = `screenshot_${domainName}_${opts.mobile ? 'mobile' : 'desktop'}.png`;
|
|
22
|
+
const targetPath = opts.outputPath || path.join(outDir, filename);
|
|
23
|
+
if (!fs.existsSync(outDir)) {
|
|
24
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
25
|
+
}
|
|
26
|
+
const browser = await chromium.launch({ headless: true });
|
|
27
|
+
try {
|
|
28
|
+
// Use iPhone 13 device profile for mobile emulation, or regular desktop viewport
|
|
29
|
+
const contextOptions = opts.mobile
|
|
30
|
+
? { ...devices['iPhone 13'] }
|
|
31
|
+
: { viewport: { width, height } };
|
|
32
|
+
const context = await browser.newContext(contextOptions);
|
|
33
|
+
const page = await context.newPage();
|
|
34
|
+
// Navigate to the target page and wait until network is quiet
|
|
35
|
+
await page.goto(url, { waitUntil: 'networkidle', timeout: 30000 });
|
|
36
|
+
await page.screenshot({
|
|
37
|
+
path: targetPath,
|
|
38
|
+
fullPage: opts.fullPage || false,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
await browser.close();
|
|
43
|
+
}
|
|
44
|
+
return targetPath;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import http from 'http';
|
|
2
|
+
import url from 'url';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import { ConfigManager } from '../config/index.js';
|
|
7
|
+
import { QrEngine } from '../qr/index.js';
|
|
8
|
+
import { ScreenshotEngine } from '../screenshot/index.js';
|
|
9
|
+
export class RestApiServer {
|
|
10
|
+
server = null;
|
|
11
|
+
start(port) {
|
|
12
|
+
return new Promise((resolve, reject) => {
|
|
13
|
+
this.server = http.createServer(async (req, res) => {
|
|
14
|
+
const parsedUrl = url.parse(req.url || '', true);
|
|
15
|
+
const pathname = parsedUrl.pathname;
|
|
16
|
+
const query = parsedUrl.query;
|
|
17
|
+
// CORS Headers
|
|
18
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
19
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
|
|
20
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
21
|
+
if (req.method === 'OPTIONS') {
|
|
22
|
+
res.writeHead(204);
|
|
23
|
+
res.end();
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
console.log(`[REST Server] ${chalk.green(req.method)} ${req.url}`);
|
|
27
|
+
try {
|
|
28
|
+
if (pathname === '/api/config') {
|
|
29
|
+
const config = ConfigManager.load();
|
|
30
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
31
|
+
res.end(JSON.stringify(config, null, 2));
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (pathname === '/api/qr') {
|
|
35
|
+
const text = query.text || 'https://kontyra.com';
|
|
36
|
+
const size = parseInt(query.size, 10) || 300;
|
|
37
|
+
// Generate temporary QR
|
|
38
|
+
const tempDir = './temp_server_files';
|
|
39
|
+
const qrPath = await QrEngine.generate(text, {
|
|
40
|
+
outputDir: tempDir,
|
|
41
|
+
outputPath: path.join(tempDir, `qr_${Date.now()}.png`),
|
|
42
|
+
size,
|
|
43
|
+
});
|
|
44
|
+
const imgBytes = fs.readFileSync(qrPath);
|
|
45
|
+
res.writeHead(200, { 'Content-Type': 'image/png' });
|
|
46
|
+
res.end(imgBytes);
|
|
47
|
+
// Clean up temp file
|
|
48
|
+
fs.unlinkSync(qrPath);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (pathname === '/api/screenshot') {
|
|
52
|
+
const siteUrl = query.url;
|
|
53
|
+
if (!siteUrl) {
|
|
54
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
55
|
+
res.end(JSON.stringify({ error: 'Missing required "url" parameter.' }));
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const mobile = query.mobile === 'true';
|
|
59
|
+
const tempDir = './temp_server_files';
|
|
60
|
+
const scrPath = await ScreenshotEngine.capture(siteUrl, {
|
|
61
|
+
outputDir: tempDir,
|
|
62
|
+
outputPath: path.join(tempDir, `scr_${Date.now()}.png`),
|
|
63
|
+
mobile,
|
|
64
|
+
});
|
|
65
|
+
const imgBytes = fs.readFileSync(scrPath);
|
|
66
|
+
res.writeHead(200, { 'Content-Type': 'image/png' });
|
|
67
|
+
res.end(imgBytes);
|
|
68
|
+
// Clean up temp file
|
|
69
|
+
fs.unlinkSync(scrPath);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
// 404 Route
|
|
73
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
74
|
+
res.end(JSON.stringify({ error: 'Endpoint not found. Exposing: /api/config, /api/qr, /api/screenshot' }));
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
console.error('[REST Server] Error handling request:', err);
|
|
78
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
79
|
+
res.end(JSON.stringify({ error: err.message || 'Internal Server Error' }));
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
this.server.listen(port, () => {
|
|
83
|
+
console.log(chalk.bold.green(`\n[REST Server] Running at http://localhost:${port}/`));
|
|
84
|
+
console.log(chalk.dim(`Exposed endpoints:`));
|
|
85
|
+
console.log(` - ${chalk.cyan(`GET http://localhost:${port}/api/config`)}`);
|
|
86
|
+
console.log(` - ${chalk.cyan(`GET http://localhost:${port}/api/qr?text=...&size=...`)}`);
|
|
87
|
+
console.log(` - ${chalk.cyan(`GET http://localhost:${port}/api/screenshot?url=...&mobile=...`)}\n`);
|
|
88
|
+
resolve();
|
|
89
|
+
});
|
|
90
|
+
this.server.on('error', (err) => {
|
|
91
|
+
reject(err);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
stop() {
|
|
96
|
+
if (this.server) {
|
|
97
|
+
this.server.close();
|
|
98
|
+
console.log(chalk.yellow('[REST Server] Stopped.'));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface Text2ImgOptions {
|
|
2
|
+
theme?: 'dark' | 'light' | 'glass';
|
|
3
|
+
type?: 'quote' | 'banner' | 'poster' | 'social';
|
|
4
|
+
title?: string;
|
|
5
|
+
author?: string;
|
|
6
|
+
outputDir?: string;
|
|
7
|
+
outputPath?: string;
|
|
8
|
+
}
|
|
9
|
+
export declare class Text2ImgEngine {
|
|
10
|
+
/**
|
|
11
|
+
* Generates a beautifully-designed social banner, quote, poster, or card from text.
|
|
12
|
+
*/
|
|
13
|
+
static generate(text: string, opts?: Text2ImgOptions): Promise<string>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { chromium } from 'playwright';
|
|
4
|
+
import { getHtmlForSocialCard } from '../../utils/templates.js';
|
|
5
|
+
import { ConfigManager } from '../config/index.js';
|
|
6
|
+
export class Text2ImgEngine {
|
|
7
|
+
/**
|
|
8
|
+
* Generates a beautifully-designed social banner, quote, poster, or card from text.
|
|
9
|
+
*/
|
|
10
|
+
static async generate(text, opts = {}) {
|
|
11
|
+
const theme = opts.theme || 'dark';
|
|
12
|
+
const type = opts.type || 'social';
|
|
13
|
+
const title = opts.title || '';
|
|
14
|
+
const author = opts.author || '';
|
|
15
|
+
const htmlContent = getHtmlForSocialCard({
|
|
16
|
+
text,
|
|
17
|
+
theme,
|
|
18
|
+
type,
|
|
19
|
+
title,
|
|
20
|
+
author,
|
|
21
|
+
});
|
|
22
|
+
// Define resolutions for screenshot viewports
|
|
23
|
+
let width = 1200;
|
|
24
|
+
let height = 630;
|
|
25
|
+
if (type === 'quote') {
|
|
26
|
+
width = 800;
|
|
27
|
+
height = 800;
|
|
28
|
+
}
|
|
29
|
+
else if (type === 'poster') {
|
|
30
|
+
width = 800;
|
|
31
|
+
height = 1200;
|
|
32
|
+
}
|
|
33
|
+
else if (type === 'banner') {
|
|
34
|
+
width = 1200;
|
|
35
|
+
height = 400;
|
|
36
|
+
}
|
|
37
|
+
// Slugify text to create a clean filename
|
|
38
|
+
const slug = text
|
|
39
|
+
.toLowerCase()
|
|
40
|
+
.replace(/[^a-z0-9]+/g, '_')
|
|
41
|
+
.substring(0, 20) || 'social_card';
|
|
42
|
+
const outDir = opts.outputDir || ConfigManager.getOutputFolder();
|
|
43
|
+
const targetPath = opts.outputPath || path.join(outDir, `${slug}.png`);
|
|
44
|
+
if (!fs.existsSync(outDir)) {
|
|
45
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
46
|
+
}
|
|
47
|
+
const browser = await chromium.launch({ headless: true });
|
|
48
|
+
try {
|
|
49
|
+
const page = await browser.newPage();
|
|
50
|
+
await page.setViewportSize({ width, height });
|
|
51
|
+
await page.setContent(htmlContent, { waitUntil: 'load' });
|
|
52
|
+
// Wait for any external font resources to load
|
|
53
|
+
await page.evaluate(() => document.fonts.ready);
|
|
54
|
+
await page.screenshot({
|
|
55
|
+
path: targetPath,
|
|
56
|
+
type: 'png',
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
await browser.close();
|
|
61
|
+
}
|
|
62
|
+
return targetPath;
|
|
63
|
+
}
|
|
64
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|