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.
Files changed (48) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/CODE_OF_CONDUCT.md +41 -0
  3. package/CONTRIBUTING.md +50 -0
  4. package/LICENSE +21 -0
  5. package/README.md +193 -0
  6. package/RELEASE.md +38 -0
  7. package/SECURITY.md +24 -0
  8. package/bin/mediaguru.js +2 -0
  9. package/dist/cli/interactive.d.ts +1 -0
  10. package/dist/cli/interactive.js +647 -0
  11. package/dist/core/batch/index.d.ts +16 -0
  12. package/dist/core/batch/index.js +66 -0
  13. package/dist/core/compress/index.d.ts +17 -0
  14. package/dist/core/compress/index.js +96 -0
  15. package/dist/core/config/index.d.ts +14 -0
  16. package/dist/core/config/index.js +56 -0
  17. package/dist/core/export/index.d.ts +12 -0
  18. package/dist/core/export/index.js +82 -0
  19. package/dist/core/image/index.d.ts +44 -0
  20. package/dist/core/image/index.js +206 -0
  21. package/dist/core/ocr/index.d.ts +14 -0
  22. package/dist/core/ocr/index.js +53 -0
  23. package/dist/core/pdf/index.d.ts +34 -0
  24. package/dist/core/pdf/index.js +121 -0
  25. package/dist/core/qr/index.d.ts +12 -0
  26. package/dist/core/qr/index.js +37 -0
  27. package/dist/core/screenshot/index.d.ts +12 -0
  28. package/dist/core/screenshot/index.js +46 -0
  29. package/dist/core/server/index.d.ts +5 -0
  30. package/dist/core/server/index.js +101 -0
  31. package/dist/core/text2img/index.d.ts +14 -0
  32. package/dist/core/text2img/index.js +64 -0
  33. package/dist/index.d.ts +1 -0
  34. package/dist/index.js +429 -0
  35. package/dist/plugins/index.d.ts +41 -0
  36. package/dist/plugins/index.js +61 -0
  37. package/dist/tests/test.d.ts +1 -0
  38. package/dist/tests/test.js +108 -0
  39. package/dist/tests/test_playwright.d.ts +1 -0
  40. package/dist/tests/test_playwright.js +60 -0
  41. package/dist/utils/branding.d.ts +6 -0
  42. package/dist/utils/branding.js +21 -0
  43. package/dist/utils/file.d.ts +7 -0
  44. package/dist/utils/file.js +29 -0
  45. package/dist/utils/templates.d.ts +9 -0
  46. package/dist/utils/templates.js +347 -0
  47. package/mediaguru-1.0.0.tgz +0 -0
  48. 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,5 @@
1
+ export declare class RestApiServer {
2
+ private server;
3
+ start(port: number): Promise<void>;
4
+ stop(): void;
5
+ }
@@ -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
+ }
@@ -0,0 +1 @@
1
+ export {};