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,647 @@
|
|
|
1
|
+
import inquirer from 'inquirer';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import { printHeader, printFooter } from '../utils/branding.js';
|
|
6
|
+
import { PdfEngine } from '../core/pdf/index.js';
|
|
7
|
+
import { ImageEngine } from '../core/image/index.js';
|
|
8
|
+
import { QrEngine } from '../core/qr/index.js';
|
|
9
|
+
import { OcrEngine } from '../core/ocr/index.js';
|
|
10
|
+
import { ScreenshotEngine } from '../core/screenshot/index.js';
|
|
11
|
+
import { Text2ImgEngine } from '../core/text2img/index.js';
|
|
12
|
+
import { CompressEngine } from '../core/compress/index.js';
|
|
13
|
+
import { ConfigManager } from '../core/config/index.js';
|
|
14
|
+
import { formatBytes } from '../utils/file.js';
|
|
15
|
+
import { RestApiServer } from '../core/server/index.js';
|
|
16
|
+
export async function launchInteractive() {
|
|
17
|
+
let exiting = false;
|
|
18
|
+
while (!exiting) {
|
|
19
|
+
printHeader();
|
|
20
|
+
const { choice } = await inquirer.prompt([
|
|
21
|
+
{
|
|
22
|
+
type: 'list',
|
|
23
|
+
name: 'choice',
|
|
24
|
+
message: 'Welcome to MediaGuru. Select a processing tool:',
|
|
25
|
+
choices: [
|
|
26
|
+
{ name: '1. 📄 PDF Tools', value: 'pdf' },
|
|
27
|
+
{ name: '2. 🖼️ Image Tools', value: 'image' },
|
|
28
|
+
{ name: '3. 👁️ OCR Engine', value: 'ocr' },
|
|
29
|
+
{ name: '4. 🔗 QR Generator', value: 'qr' },
|
|
30
|
+
{ name: '5. 🌐 Website Screenshot', value: 'screenshot' },
|
|
31
|
+
{ name: '6. 🎨 Text-to-Image (Social Card)', value: 'text2img' },
|
|
32
|
+
{ name: '7. 📦 Compression', value: 'compress' },
|
|
33
|
+
{ name: '8. 🌐 Launch REST API Server', value: 'server' },
|
|
34
|
+
{ name: '9. ⚙️ Configuration', value: 'config' },
|
|
35
|
+
{ name: '10. ❌ Exit', value: 'exit' },
|
|
36
|
+
],
|
|
37
|
+
},
|
|
38
|
+
]);
|
|
39
|
+
switch (choice) {
|
|
40
|
+
case 'pdf':
|
|
41
|
+
await handlePdfMenu();
|
|
42
|
+
break;
|
|
43
|
+
case 'image':
|
|
44
|
+
await handleImageMenu();
|
|
45
|
+
break;
|
|
46
|
+
case 'ocr':
|
|
47
|
+
await handleOcrMenu();
|
|
48
|
+
break;
|
|
49
|
+
case 'qr':
|
|
50
|
+
await handleQrMenu();
|
|
51
|
+
break;
|
|
52
|
+
case 'screenshot':
|
|
53
|
+
await handleScreenshotMenu();
|
|
54
|
+
break;
|
|
55
|
+
case 'text2img':
|
|
56
|
+
await handleText2ImgMenu();
|
|
57
|
+
break;
|
|
58
|
+
case 'compress':
|
|
59
|
+
await handleCompressMenu();
|
|
60
|
+
break;
|
|
61
|
+
case 'server':
|
|
62
|
+
await handleServerMenu();
|
|
63
|
+
break;
|
|
64
|
+
case 'config':
|
|
65
|
+
await handleConfigMenu();
|
|
66
|
+
break;
|
|
67
|
+
case 'exit':
|
|
68
|
+
exiting = true;
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
if (!exiting) {
|
|
72
|
+
console.log('\n');
|
|
73
|
+
await inquirer.prompt([
|
|
74
|
+
{
|
|
75
|
+
type: 'input',
|
|
76
|
+
name: 'continue',
|
|
77
|
+
message: 'Press ENTER to return to the main menu...',
|
|
78
|
+
},
|
|
79
|
+
]);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
printHeader();
|
|
83
|
+
console.log(chalk.green.bold('\nThank you for using MediaGuru! Have a wonderful day! ✨'));
|
|
84
|
+
printFooter();
|
|
85
|
+
}
|
|
86
|
+
async function handlePdfMenu() {
|
|
87
|
+
const { action } = await inquirer.prompt([
|
|
88
|
+
{
|
|
89
|
+
type: 'list',
|
|
90
|
+
name: 'action',
|
|
91
|
+
message: 'Select a PDF tool operation:',
|
|
92
|
+
choices: [
|
|
93
|
+
{ name: 'Markdown to PDF', value: 'md2pdf' },
|
|
94
|
+
{ name: 'HTML to PDF', value: 'html2pdf' },
|
|
95
|
+
{ name: 'Merge PDFs', value: 'merge' },
|
|
96
|
+
{ name: 'Split PDF', value: 'split' },
|
|
97
|
+
{ name: 'Extract Text', value: 'extract' },
|
|
98
|
+
{ name: 'Compress PDF', value: 'compress' },
|
|
99
|
+
{ name: '← Back to Main Menu', value: 'back' },
|
|
100
|
+
],
|
|
101
|
+
},
|
|
102
|
+
]);
|
|
103
|
+
if (action === 'back')
|
|
104
|
+
return;
|
|
105
|
+
if (action === 'md2pdf') {
|
|
106
|
+
const { path: mdPath } = await inquirer.prompt([
|
|
107
|
+
{
|
|
108
|
+
type: 'input',
|
|
109
|
+
name: 'path',
|
|
110
|
+
message: 'Enter path to Markdown file:',
|
|
111
|
+
validate: (input) => fs.existsSync(input) ? true : 'File does not exist.',
|
|
112
|
+
},
|
|
113
|
+
]);
|
|
114
|
+
const spinner = ora('Converting Markdown to PDF...').start();
|
|
115
|
+
try {
|
|
116
|
+
const out = await PdfEngine.markdownToPdf(mdPath);
|
|
117
|
+
spinner.succeed(`PDF created successfully: ${chalk.cyan(out)}`);
|
|
118
|
+
}
|
|
119
|
+
catch (e) {
|
|
120
|
+
spinner.fail(`Failed: ${e.message}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
else if (action === 'html2pdf') {
|
|
124
|
+
const { path: htmlPath } = await inquirer.prompt([
|
|
125
|
+
{
|
|
126
|
+
type: 'input',
|
|
127
|
+
name: 'path',
|
|
128
|
+
message: 'Enter path to HTML file:',
|
|
129
|
+
validate: (input) => fs.existsSync(input) ? true : 'File does not exist.',
|
|
130
|
+
},
|
|
131
|
+
]);
|
|
132
|
+
const spinner = ora('Converting HTML to PDF...').start();
|
|
133
|
+
try {
|
|
134
|
+
const out = await PdfEngine.htmlToPdf(htmlPath);
|
|
135
|
+
spinner.succeed(`PDF created successfully: ${chalk.cyan(out)}`);
|
|
136
|
+
}
|
|
137
|
+
catch (e) {
|
|
138
|
+
spinner.fail(`Failed: ${e.message}`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
else if (action === 'merge') {
|
|
142
|
+
const { list } = await inquirer.prompt([
|
|
143
|
+
{
|
|
144
|
+
type: 'input',
|
|
145
|
+
name: 'list',
|
|
146
|
+
message: 'Enter PDF file paths to merge (comma separated):',
|
|
147
|
+
validate: (input) => {
|
|
148
|
+
const files = input.split(',').map((f) => f.trim());
|
|
149
|
+
if (files.length < 2)
|
|
150
|
+
return 'Please specify at least 2 files.';
|
|
151
|
+
for (const f of files) {
|
|
152
|
+
if (!fs.existsSync(f))
|
|
153
|
+
return `File does not exist: ${f}`;
|
|
154
|
+
}
|
|
155
|
+
return true;
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
]);
|
|
159
|
+
const files = list.split(',').map((f) => f.trim());
|
|
160
|
+
const spinner = ora('Merging PDFs...').start();
|
|
161
|
+
try {
|
|
162
|
+
const out = await PdfEngine.merge(files);
|
|
163
|
+
spinner.succeed(`PDFs merged successfully: ${chalk.cyan(out)}`);
|
|
164
|
+
}
|
|
165
|
+
catch (e) {
|
|
166
|
+
spinner.fail(`Failed: ${e.message}`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
else if (action === 'split') {
|
|
170
|
+
const { path: pdfPath } = await inquirer.prompt([
|
|
171
|
+
{
|
|
172
|
+
type: 'input',
|
|
173
|
+
name: 'path',
|
|
174
|
+
message: 'Enter path to PDF file:',
|
|
175
|
+
validate: (input) => fs.existsSync(input) ? true : 'File does not exist.',
|
|
176
|
+
},
|
|
177
|
+
]);
|
|
178
|
+
const spinner = ora('Splitting PDF pages...').start();
|
|
179
|
+
try {
|
|
180
|
+
const pages = await PdfEngine.split(pdfPath);
|
|
181
|
+
spinner.succeed(`Split into ${pages.length} pages:`);
|
|
182
|
+
pages.forEach((p) => console.log(` - ${chalk.cyan(p)}`));
|
|
183
|
+
}
|
|
184
|
+
catch (e) {
|
|
185
|
+
spinner.fail(`Failed: ${e.message}`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
else if (action === 'extract') {
|
|
189
|
+
const { path: pdfPath } = await inquirer.prompt([
|
|
190
|
+
{
|
|
191
|
+
type: 'input',
|
|
192
|
+
name: 'path',
|
|
193
|
+
message: 'Enter path to PDF file:',
|
|
194
|
+
validate: (input) => fs.existsSync(input) ? true : 'File does not exist.',
|
|
195
|
+
},
|
|
196
|
+
]);
|
|
197
|
+
const spinner = ora('Extracting PDF text...').start();
|
|
198
|
+
try {
|
|
199
|
+
const text = await PdfEngine.extractText(pdfPath);
|
|
200
|
+
spinner.succeed('Extraction completed. Text Preview:');
|
|
201
|
+
console.log(chalk.dim('\n' + '─'.repeat(40)));
|
|
202
|
+
console.log(text.substring(0, 1000) + (text.length > 1000 ? '\n...[truncated]' : ''));
|
|
203
|
+
console.log(chalk.dim('─'.repeat(40)));
|
|
204
|
+
}
|
|
205
|
+
catch (e) {
|
|
206
|
+
spinner.fail(`Failed: ${e.message}`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
else if (action === 'compress') {
|
|
210
|
+
const { path: pdfPath } = await inquirer.prompt([
|
|
211
|
+
{
|
|
212
|
+
type: 'input',
|
|
213
|
+
name: 'path',
|
|
214
|
+
message: 'Enter path to PDF file:',
|
|
215
|
+
validate: (input) => fs.existsSync(input) ? true : 'File does not exist.',
|
|
216
|
+
},
|
|
217
|
+
]);
|
|
218
|
+
const spinner = ora('Optimizing and compressing PDF...').start();
|
|
219
|
+
try {
|
|
220
|
+
const out = await PdfEngine.compress(pdfPath);
|
|
221
|
+
spinner.succeed(`PDF compressed successfully: ${chalk.cyan(out)}`);
|
|
222
|
+
}
|
|
223
|
+
catch (e) {
|
|
224
|
+
spinner.fail(`Failed: ${e.message}`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
async function handleImageMenu() {
|
|
229
|
+
const { action } = await inquirer.prompt([
|
|
230
|
+
{
|
|
231
|
+
type: 'list',
|
|
232
|
+
name: 'action',
|
|
233
|
+
message: 'Select an Image tool operation:',
|
|
234
|
+
choices: [
|
|
235
|
+
{ name: 'Resize Image', value: 'resize' },
|
|
236
|
+
{ name: 'Convert Format', value: 'convert' },
|
|
237
|
+
{ name: 'Overlay Watermark', value: 'watermark' },
|
|
238
|
+
{ name: 'Remove Background', value: 'remove-bg' },
|
|
239
|
+
{ name: '← Back to Main Menu', value: 'back' },
|
|
240
|
+
],
|
|
241
|
+
},
|
|
242
|
+
]);
|
|
243
|
+
if (action === 'back')
|
|
244
|
+
return;
|
|
245
|
+
const { path: imgPath } = await inquirer.prompt([
|
|
246
|
+
{
|
|
247
|
+
type: 'input',
|
|
248
|
+
name: 'path',
|
|
249
|
+
message: 'Enter image file path:',
|
|
250
|
+
validate: (input) => fs.existsSync(input) ? true : 'File does not exist.',
|
|
251
|
+
},
|
|
252
|
+
]);
|
|
253
|
+
if (action === 'resize') {
|
|
254
|
+
const { size } = await inquirer.prompt([
|
|
255
|
+
{
|
|
256
|
+
type: 'input',
|
|
257
|
+
name: 'size',
|
|
258
|
+
message: 'Enter target size (e.g. 800x600 or 800):',
|
|
259
|
+
default: '800x600',
|
|
260
|
+
},
|
|
261
|
+
]);
|
|
262
|
+
const spinner = ora('Resizing image...').start();
|
|
263
|
+
try {
|
|
264
|
+
const out = await ImageEngine.resize(imgPath, size);
|
|
265
|
+
spinner.succeed(`Resized image saved: ${chalk.cyan(out)}`);
|
|
266
|
+
}
|
|
267
|
+
catch (e) {
|
|
268
|
+
spinner.fail(`Failed: ${e.message}`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
else if (action === 'convert') {
|
|
272
|
+
const { format } = await inquirer.prompt([
|
|
273
|
+
{
|
|
274
|
+
type: 'list',
|
|
275
|
+
name: 'format',
|
|
276
|
+
message: 'Select target format:',
|
|
277
|
+
choices: ['webp', 'png', 'jpg', 'jpeg'],
|
|
278
|
+
},
|
|
279
|
+
]);
|
|
280
|
+
const spinner = ora('Converting format...').start();
|
|
281
|
+
try {
|
|
282
|
+
const out = await ImageEngine.convert(imgPath, format);
|
|
283
|
+
spinner.succeed(`Converted image saved: ${chalk.cyan(out)}`);
|
|
284
|
+
}
|
|
285
|
+
catch (e) {
|
|
286
|
+
spinner.fail(`Failed: ${e.message}`);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
else if (action === 'watermark') {
|
|
290
|
+
const { wmPath } = await inquirer.prompt([
|
|
291
|
+
{
|
|
292
|
+
type: 'input',
|
|
293
|
+
name: 'wmPath',
|
|
294
|
+
message: 'Enter path to Watermark logo/image:',
|
|
295
|
+
validate: (input) => fs.existsSync(input) ? true : 'File does not exist.',
|
|
296
|
+
},
|
|
297
|
+
]);
|
|
298
|
+
const spinner = ora('Overlaying watermark...').start();
|
|
299
|
+
try {
|
|
300
|
+
const out = await ImageEngine.watermark(imgPath, wmPath);
|
|
301
|
+
spinner.succeed(`Watermarked image saved: ${chalk.cyan(out)}`);
|
|
302
|
+
}
|
|
303
|
+
catch (e) {
|
|
304
|
+
spinner.fail(`Failed: ${e.message}`);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
else if (action === 'remove-bg') {
|
|
308
|
+
const { engine } = await inquirer.prompt([
|
|
309
|
+
{
|
|
310
|
+
type: 'list',
|
|
311
|
+
name: 'engine',
|
|
312
|
+
message: 'Select background removal engine:',
|
|
313
|
+
choices: [
|
|
314
|
+
{ name: 'Local (Color chroma thresholding)', value: 'local' },
|
|
315
|
+
{ name: 'API (remove.bg - needs API key)', value: 'api' },
|
|
316
|
+
],
|
|
317
|
+
},
|
|
318
|
+
]);
|
|
319
|
+
const spinner = ora('Extracting and removing background...').start();
|
|
320
|
+
try {
|
|
321
|
+
const out = await ImageEngine.removeBg(imgPath, engine);
|
|
322
|
+
spinner.succeed(`Background removed. Image saved: ${chalk.cyan(out)}`);
|
|
323
|
+
}
|
|
324
|
+
catch (e) {
|
|
325
|
+
spinner.fail(`Failed: ${e.message}`);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
async function handleOcrMenu() {
|
|
330
|
+
const { path: imgPath } = await inquirer.prompt([
|
|
331
|
+
{
|
|
332
|
+
type: 'input',
|
|
333
|
+
name: 'path',
|
|
334
|
+
message: 'Enter image file path for OCR:',
|
|
335
|
+
validate: (input) => fs.existsSync(input) ? true : 'File does not exist.',
|
|
336
|
+
},
|
|
337
|
+
]);
|
|
338
|
+
const { format } = await inquirer.prompt([
|
|
339
|
+
{
|
|
340
|
+
type: 'list',
|
|
341
|
+
name: 'format',
|
|
342
|
+
message: 'Export text to file format?',
|
|
343
|
+
choices: [
|
|
344
|
+
{ name: 'None (Console only)', value: 'none' },
|
|
345
|
+
{ name: 'Text file (.txt)', value: 'txt' },
|
|
346
|
+
{ name: 'Markdown file (.md)', value: 'markdown' },
|
|
347
|
+
{ name: 'Structured JSON (.json)', value: 'json' },
|
|
348
|
+
],
|
|
349
|
+
},
|
|
350
|
+
]);
|
|
351
|
+
const spinner = ora('Parsing text from image (running OCR)...').start();
|
|
352
|
+
try {
|
|
353
|
+
const exportOpt = format === 'none' ? undefined : format;
|
|
354
|
+
const res = await OcrEngine.extract(imgPath, { exportFormat: exportOpt });
|
|
355
|
+
spinner.succeed('OCR Process Completed!');
|
|
356
|
+
console.log(chalk.yellow.bold('\nExtracted Text:'));
|
|
357
|
+
console.log(chalk.dim('─'.repeat(40)));
|
|
358
|
+
console.log(res.text.trim());
|
|
359
|
+
console.log(chalk.dim('─'.repeat(40)));
|
|
360
|
+
if (res.exportedPath) {
|
|
361
|
+
console.log(chalk.green(`\nExported file: ${chalk.bold(res.exportedPath)}`));
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
catch (e) {
|
|
365
|
+
spinner.fail(`OCR Failed: ${e.message}`);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
async function handleQrMenu() {
|
|
369
|
+
const { text } = await inquirer.prompt([
|
|
370
|
+
{
|
|
371
|
+
type: 'input',
|
|
372
|
+
name: 'text',
|
|
373
|
+
message: 'Enter URL or Text to encode in QR:',
|
|
374
|
+
default: 'https://kontyra.com',
|
|
375
|
+
},
|
|
376
|
+
]);
|
|
377
|
+
const { svg } = await inquirer.prompt([
|
|
378
|
+
{
|
|
379
|
+
type: 'confirm',
|
|
380
|
+
name: 'svg',
|
|
381
|
+
message: 'Generate vector SVG instead of binary PNG?',
|
|
382
|
+
default: false,
|
|
383
|
+
},
|
|
384
|
+
]);
|
|
385
|
+
const { size } = await inquirer.prompt([
|
|
386
|
+
{
|
|
387
|
+
type: 'number',
|
|
388
|
+
name: 'size',
|
|
389
|
+
message: 'Enter square size in pixels:',
|
|
390
|
+
default: 300,
|
|
391
|
+
},
|
|
392
|
+
]);
|
|
393
|
+
const spinner = ora('Generating QR Code...').start();
|
|
394
|
+
try {
|
|
395
|
+
const out = await QrEngine.generate(text, { svg, size });
|
|
396
|
+
spinner.succeed(`QR Code generated: ${chalk.cyan(out)}`);
|
|
397
|
+
}
|
|
398
|
+
catch (e) {
|
|
399
|
+
spinner.fail(`Failed: ${e.message}`);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
async function handleScreenshotMenu() {
|
|
403
|
+
const { url } = await inquirer.prompt([
|
|
404
|
+
{
|
|
405
|
+
type: 'input',
|
|
406
|
+
name: 'url',
|
|
407
|
+
message: 'Enter website URL to screenshot:',
|
|
408
|
+
default: 'https://example.com',
|
|
409
|
+
},
|
|
410
|
+
]);
|
|
411
|
+
const { fullPage } = await inquirer.prompt([
|
|
412
|
+
{
|
|
413
|
+
type: 'confirm',
|
|
414
|
+
name: 'fullPage',
|
|
415
|
+
message: 'Capture full scrollable page?',
|
|
416
|
+
default: false,
|
|
417
|
+
},
|
|
418
|
+
]);
|
|
419
|
+
const { mobile } = await inquirer.prompt([
|
|
420
|
+
{
|
|
421
|
+
type: 'confirm',
|
|
422
|
+
name: 'mobile',
|
|
423
|
+
message: 'Emulate responsive mobile viewport?',
|
|
424
|
+
default: false,
|
|
425
|
+
},
|
|
426
|
+
]);
|
|
427
|
+
const spinner = ora('Launching browser and rendering website screenshot...').start();
|
|
428
|
+
try {
|
|
429
|
+
const out = await ScreenshotEngine.capture(url, { fullPage, mobile });
|
|
430
|
+
spinner.succeed(`Screenshot saved: ${chalk.cyan(out)}`);
|
|
431
|
+
}
|
|
432
|
+
catch (e) {
|
|
433
|
+
spinner.fail(`Failed: ${e.message}`);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
async function handleText2ImgMenu() {
|
|
437
|
+
const { text } = await inquirer.prompt([
|
|
438
|
+
{
|
|
439
|
+
type: 'input',
|
|
440
|
+
name: 'text',
|
|
441
|
+
message: 'Enter message/quote for the social card:',
|
|
442
|
+
default: 'Welcome to Kontyra',
|
|
443
|
+
},
|
|
444
|
+
]);
|
|
445
|
+
const { type } = await inquirer.prompt([
|
|
446
|
+
{
|
|
447
|
+
type: 'list',
|
|
448
|
+
name: 'type',
|
|
449
|
+
message: 'Select card template type:',
|
|
450
|
+
choices: [
|
|
451
|
+
{ name: 'Social Post Card (1200x630)', value: 'social' },
|
|
452
|
+
{ name: 'Quote Block (800x800)', value: 'quote' },
|
|
453
|
+
{ name: 'Announcements Poster (800x1200)', value: 'poster' },
|
|
454
|
+
{ name: 'Header Banner (1200x400)', value: 'banner' },
|
|
455
|
+
],
|
|
456
|
+
},
|
|
457
|
+
]);
|
|
458
|
+
const { theme } = await inquirer.prompt([
|
|
459
|
+
{
|
|
460
|
+
type: 'list',
|
|
461
|
+
name: 'theme',
|
|
462
|
+
message: 'Select visual color theme:',
|
|
463
|
+
choices: [
|
|
464
|
+
{ name: 'Dark Mode (Radial slate gradient)', value: 'dark' },
|
|
465
|
+
{ name: 'Light Mode (Linear soft blue gradient)', value: 'light' },
|
|
466
|
+
{ name: 'Glassmorphic (Airy pastel blurred canvas)', value: 'glass' },
|
|
467
|
+
],
|
|
468
|
+
},
|
|
469
|
+
]);
|
|
470
|
+
const { title } = await inquirer.prompt([
|
|
471
|
+
{
|
|
472
|
+
type: 'input',
|
|
473
|
+
name: 'title',
|
|
474
|
+
message: 'Enter category/title label (optional):',
|
|
475
|
+
default: '',
|
|
476
|
+
},
|
|
477
|
+
]);
|
|
478
|
+
const { author } = await inquirer.prompt([
|
|
479
|
+
{
|
|
480
|
+
type: 'input',
|
|
481
|
+
name: 'author',
|
|
482
|
+
message: 'Enter author name label (optional):',
|
|
483
|
+
default: '',
|
|
484
|
+
},
|
|
485
|
+
]);
|
|
486
|
+
const spinner = ora('Compiling card design in browser...').start();
|
|
487
|
+
try {
|
|
488
|
+
const out = await Text2ImgEngine.generate(text, {
|
|
489
|
+
type,
|
|
490
|
+
theme,
|
|
491
|
+
title,
|
|
492
|
+
author,
|
|
493
|
+
});
|
|
494
|
+
spinner.succeed(`Social card exported: ${chalk.cyan(out)}`);
|
|
495
|
+
}
|
|
496
|
+
catch (e) {
|
|
497
|
+
spinner.fail(`Failed: ${e.message}`);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
async function handleCompressMenu() {
|
|
501
|
+
const { type } = await inquirer.prompt([
|
|
502
|
+
{
|
|
503
|
+
type: 'list',
|
|
504
|
+
name: 'type',
|
|
505
|
+
message: 'What would you like to compress?',
|
|
506
|
+
choices: [
|
|
507
|
+
{ name: 'Single image file', value: 'file' },
|
|
508
|
+
{ name: 'Entire folder recursively', value: 'folder' },
|
|
509
|
+
],
|
|
510
|
+
},
|
|
511
|
+
]);
|
|
512
|
+
if (type === 'file') {
|
|
513
|
+
const { path: fileP } = await inquirer.prompt([
|
|
514
|
+
{
|
|
515
|
+
type: 'input',
|
|
516
|
+
name: 'path',
|
|
517
|
+
message: 'Enter image file path:',
|
|
518
|
+
validate: (input) => fs.existsSync(input) ? true : 'File does not exist.',
|
|
519
|
+
},
|
|
520
|
+
]);
|
|
521
|
+
const spinner = ora('Compressing image...').start();
|
|
522
|
+
try {
|
|
523
|
+
const res = await CompressEngine.compressImage(fileP);
|
|
524
|
+
spinner.succeed(`Compressed image saved: ${chalk.cyan(res.filePath)}`);
|
|
525
|
+
console.log(chalk.bold.yellow('\nCompression Report:'));
|
|
526
|
+
console.log(`- Original size: ${chalk.bold(formatBytes(res.originalSize))}`);
|
|
527
|
+
console.log(`- Compressed size: ${chalk.bold(formatBytes(res.compressedSize))}`);
|
|
528
|
+
console.log(`- Space saved: ${chalk.green.bold(formatBytes(res.savedBytes))} (${chalk.green.bold(res.percentage.toFixed(1))}% reduction)`);
|
|
529
|
+
}
|
|
530
|
+
catch (e) {
|
|
531
|
+
spinner.fail(`Failed: ${e.message}`);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
else {
|
|
535
|
+
const { path: folderP } = await inquirer.prompt([
|
|
536
|
+
{
|
|
537
|
+
type: 'input',
|
|
538
|
+
name: 'path',
|
|
539
|
+
message: 'Enter folder path:',
|
|
540
|
+
validate: (input) => fs.existsSync(input) && fs.statSync(input).isDirectory() ? true : 'Folder does not exist.',
|
|
541
|
+
},
|
|
542
|
+
]);
|
|
543
|
+
const spinner = ora('Compressing entire folder recursively...').start();
|
|
544
|
+
try {
|
|
545
|
+
const results = await CompressEngine.compressFolder(folderP);
|
|
546
|
+
spinner.succeed(`Folder compressed. Total files processed: ${results.length}`);
|
|
547
|
+
if (results.length > 0) {
|
|
548
|
+
let totalOriginal = 0;
|
|
549
|
+
let totalCompressed = 0;
|
|
550
|
+
results.forEach((r) => {
|
|
551
|
+
totalOriginal += r.originalSize;
|
|
552
|
+
totalCompressed += r.compressedSize;
|
|
553
|
+
});
|
|
554
|
+
const totalSaved = totalOriginal - totalCompressed;
|
|
555
|
+
const totalPct = totalOriginal > 0 ? (totalSaved / totalOriginal) * 100 : 0;
|
|
556
|
+
console.log(chalk.bold.yellow('\nBatch Folder Compression Report:'));
|
|
557
|
+
console.log(`- Processed files: ${chalk.cyan(results.length)}`);
|
|
558
|
+
console.log(`- Combined original size: ${chalk.bold(formatBytes(totalOriginal))}`);
|
|
559
|
+
console.log(`- Combined compressed size: ${chalk.bold(formatBytes(totalCompressed))}`);
|
|
560
|
+
console.log(`- Combined space saved: ${chalk.green.bold(formatBytes(totalSaved))} (${chalk.green.bold(totalPct.toFixed(1))}% reduction)`);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
catch (e) {
|
|
564
|
+
spinner.fail(`Failed: ${e.message}`);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
async function handleConfigMenu() {
|
|
569
|
+
const current = ConfigManager.load();
|
|
570
|
+
console.log(chalk.bold.yellow('\nCurrent Configuration:'));
|
|
571
|
+
console.log(`1. Default Image Format: ${chalk.cyan(current.defaultImageFormat)}`);
|
|
572
|
+
console.log(`2. PDF Rendering Engine: ${chalk.cyan(current.pdfEngine)}`);
|
|
573
|
+
console.log(`3. Compression Quality: ${chalk.cyan(current.compressionQuality + '%')}`);
|
|
574
|
+
console.log(`4. Screenshot Resolution: ${chalk.cyan(current.screenshotResolution)}`);
|
|
575
|
+
console.log(`5. Default Output Folder: ${chalk.cyan(current.outputFolder)}`);
|
|
576
|
+
console.log('\n');
|
|
577
|
+
const { edit } = await inquirer.prompt([
|
|
578
|
+
{
|
|
579
|
+
type: 'confirm',
|
|
580
|
+
name: 'edit',
|
|
581
|
+
message: 'Would you like to edit any settings?',
|
|
582
|
+
default: false,
|
|
583
|
+
},
|
|
584
|
+
]);
|
|
585
|
+
if (!edit)
|
|
586
|
+
return;
|
|
587
|
+
const answers = await inquirer.prompt([
|
|
588
|
+
{
|
|
589
|
+
type: 'list',
|
|
590
|
+
name: 'defaultImageFormat',
|
|
591
|
+
message: 'Select default image format:',
|
|
592
|
+
choices: ['webp', 'png', 'jpg', 'jpeg'],
|
|
593
|
+
default: current.defaultImageFormat,
|
|
594
|
+
},
|
|
595
|
+
{
|
|
596
|
+
type: 'list',
|
|
597
|
+
name: 'pdfEngine',
|
|
598
|
+
message: 'Select PDF engine:',
|
|
599
|
+
choices: ['playwright', 'local'],
|
|
600
|
+
default: current.pdfEngine,
|
|
601
|
+
},
|
|
602
|
+
{
|
|
603
|
+
type: 'number',
|
|
604
|
+
name: 'compressionQuality',
|
|
605
|
+
message: 'Enter image compression quality (1-100):',
|
|
606
|
+
default: current.compressionQuality,
|
|
607
|
+
validate: (val) => val >= 1 && val <= 100 ? true : 'Must be between 1 and 100.',
|
|
608
|
+
},
|
|
609
|
+
{
|
|
610
|
+
type: 'input',
|
|
611
|
+
name: 'screenshotResolution',
|
|
612
|
+
message: 'Enter screenshot resolution width x height:',
|
|
613
|
+
default: current.screenshotResolution,
|
|
614
|
+
validate: (input) => input.includes('x') ? true : 'Must be format like "1280x720".',
|
|
615
|
+
},
|
|
616
|
+
{
|
|
617
|
+
type: 'input',
|
|
618
|
+
name: 'outputFolder',
|
|
619
|
+
message: 'Enter default output directory path:',
|
|
620
|
+
default: current.outputFolder,
|
|
621
|
+
},
|
|
622
|
+
]);
|
|
623
|
+
ConfigManager.update(answers);
|
|
624
|
+
console.log(chalk.green.bold('\nSettings updated successfully!'));
|
|
625
|
+
}
|
|
626
|
+
async function handleServerMenu() {
|
|
627
|
+
const { port } = await inquirer.prompt([
|
|
628
|
+
{
|
|
629
|
+
type: 'number',
|
|
630
|
+
name: 'port',
|
|
631
|
+
message: 'Enter port to launch REST API server on:',
|
|
632
|
+
default: 3000,
|
|
633
|
+
validate: (val) => val > 0 && val <= 65535 ? true : 'Please enter a valid port.',
|
|
634
|
+
},
|
|
635
|
+
]);
|
|
636
|
+
const server = new RestApiServer();
|
|
637
|
+
try {
|
|
638
|
+
await server.start(port);
|
|
639
|
+
console.log(chalk.yellow('\n[REST Server] Running in foreground...'));
|
|
640
|
+
console.log(chalk.bold.red('Press Ctrl+C to terminate the server and exit MediaGuru.'));
|
|
641
|
+
// Hang process to run server
|
|
642
|
+
await new Promise(() => { });
|
|
643
|
+
}
|
|
644
|
+
catch (e) {
|
|
645
|
+
console.error(chalk.red(`Server failed to start: ${e.message}`));
|
|
646
|
+
}
|
|
647
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface BatchResult {
|
|
2
|
+
sourceFile: string;
|
|
3
|
+
outputFile?: string;
|
|
4
|
+
success: boolean;
|
|
5
|
+
error?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare class BatchEngine {
|
|
8
|
+
/**
|
|
9
|
+
* Run batch conversion on images matching a glob pattern
|
|
10
|
+
*/
|
|
11
|
+
static convertImages(pattern: string, targetFormat: string): Promise<BatchResult[]>;
|
|
12
|
+
/**
|
|
13
|
+
* Run batch PDF generation on markdown or HTML files matching a glob pattern
|
|
14
|
+
*/
|
|
15
|
+
static convertToPdf(pattern: string): Promise<BatchResult[]>;
|
|
16
|
+
}
|