CatPtain commited on
Commit
613c920
·
verified ·
1 Parent(s): 3713bef

Upload screenshotService.js

Browse files
backend/src/services/screenshotService.js CHANGED
@@ -1,18 +1,21 @@
1
  import puppeteer from 'puppeteer';
 
 
2
 
3
  class ScreenshotService {
4
  constructor() {
5
  this.browser = null;
 
6
  this.isInitialized = false;
7
  this.maxRetries = 2;
8
  this.browserLaunchRetries = 0;
9
  this.maxBrowserLaunchRetries = 2;
10
  this.isClosing = false;
11
  this.isHuggingFaceSpace = process.env.SPACE_ID || process.env.HF_SPACE_ID;
 
12
  }
13
 
14
  async initBrowser() {
15
- // 在Hugging Face Space环境中,Puppeteer可能不稳定,尝试更保守的配置
16
  if (this.isClosing) {
17
  console.log('浏览器正在关闭中,等待重新初始化...');
18
  this.browser = null;
@@ -25,7 +28,7 @@ class ScreenshotService {
25
 
26
  const launchOptions = {
27
  headless: 'new',
28
- timeout: 60000, // 增加超时时间
29
  protocolTimeout: 60000,
30
  args: [
31
  '--no-sandbox',
@@ -55,8 +58,7 @@ class ScreenshotService {
55
  '--disable-hang-monitor',
56
  '--disable-prompt-on-repost',
57
  '--memory-pressure-off',
58
- '--max_old_space_size=1024', // 增加内存限制
59
- // 添加更多Hugging Face Space兼容性选项
60
  '--disable-background-media-suspend',
61
  '--disable-backgrounding-occluded-windows',
62
  '--disable-renderer-backgrounding',
@@ -76,7 +78,6 @@ class ScreenshotService {
76
  ]
77
  };
78
 
79
- // 如果是Hugging Face Space环境,使用单进程模式
80
  if (this.isHuggingFaceSpace) {
81
  console.log('检测到Hugging Face Space环境,使用单进程模式');
82
  launchOptions.args.push(
@@ -87,7 +88,6 @@ class ScreenshotService {
87
  );
88
  }
89
 
90
- // 尝试找到Chrome可执行文件路径
91
  try {
92
  const chromePath = process.env.CHROME_BIN ||
93
  process.env.GOOGLE_CHROME_BIN ||
@@ -105,7 +105,6 @@ class ScreenshotService {
105
  this.browser = await puppeteer.launch(launchOptions);
106
  console.log('✅ Puppeteer浏览器初始化成功');
107
 
108
- // 监听浏览器断开连接事件
109
  this.browser.on('disconnected', () => {
110
  console.log('Puppeteer浏览器连接断开');
111
  this.browser = null;
@@ -119,17 +118,15 @@ class ScreenshotService {
119
 
120
  if (this.browserLaunchRetries <= this.maxBrowserLaunchRetries) {
121
  console.log(`尝试重新初始化浏览器 (${this.browserLaunchRetries}/${this.maxBrowserLaunchRetries})`);
122
- await this.delay(3000); // 增加等待时间
123
  return this.initBrowser();
124
  } else {
125
- // 如果Puppeteer完全失败,返回null,使用fallback方法
126
  console.warn('⚠️ Puppeteer初始化完全失败,将使用fallback方法');
127
  return null;
128
  }
129
  }
130
  }
131
 
132
- // 验证浏览器是否仍然连接
133
  if (this.browser) {
134
  try {
135
  await this.browser.version();
@@ -144,6 +141,56 @@ class ScreenshotService {
144
  return this.browser;
145
  }
146
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
  async closeBrowser() {
148
  if (this.browser && !this.isClosing) {
149
  try {
@@ -160,15 +207,26 @@ class ScreenshotService {
160
  }
161
  }
162
 
163
- // 延迟函数
 
 
 
 
 
 
 
 
 
 
 
 
 
164
  delay(ms) {
165
  return new Promise(resolve => setTimeout(resolve, ms));
166
  }
167
 
168
- // 从HTML中提取PPT尺寸信息
169
  extractPPTDimensions(htmlContent) {
170
  try {
171
- // 从JavaScript中提取PPT_DIMENSIONS
172
  const dimensionMatch = htmlContent.match(/window\.PPT_DIMENSIONS\s*=\s*{\s*width:\s*(\d+),\s*height:\s*(\d+)\s*}/);
173
  if (dimensionMatch) {
174
  return {
@@ -177,7 +235,6 @@ class ScreenshotService {
177
  };
178
  }
179
 
180
- // 从viewport meta标签中提取
181
  const viewportMatch = htmlContent.match(/width=(\d+),\s*height=(\d+)/);
182
  if (viewportMatch) {
183
  return {
@@ -186,7 +243,6 @@ class ScreenshotService {
186
  };
187
  }
188
 
189
- // 默认尺寸
190
  return { width: 960, height: 720 };
191
  } catch (error) {
192
  console.warn('提取PPT尺寸失败,使用默认尺寸:', error.message);
@@ -194,11 +250,9 @@ class ScreenshotService {
194
  }
195
  }
196
 
197
- // 生成简化的PNG图片作为fallback
198
  generateFallbackImage(width, height, message = 'Screenshot not available') {
199
  console.log(`🔄 生成fallback图片: ${width}x${height}`);
200
 
201
- // 创建一个简单的SVG作为fallback
202
  const svg = `
203
  <svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg">
204
  <rect width="100%" height="100%" fill="#f8f9fa"/>
@@ -222,21 +276,17 @@ class ScreenshotService {
222
  </svg>
223
  `;
224
 
225
- // 返回简单的占位图片数据
226
  return Buffer.from(svg, 'utf8');
227
  }
228
 
229
- // 优化HTML内容以确保精确截图
230
  optimizeHtmlForScreenshot(htmlContent, targetWidth, targetHeight) {
231
  console.log(`优化HTML for精确截图, 目标尺寸: ${targetWidth}x${targetHeight}`);
232
 
233
- // 在<head>标签后立即插入截图优化代码
234
  const optimizedHtml = htmlContent.replace(
235
  /(<head[^>]*>)/i,
236
  `$1
237
  <meta name="screenshot-mode" content="true">
238
  <style id="screenshot-precise-control">
239
- /* 截图模式:绝对精确的尺寸控制 */
240
  *, *::before, *::after {
241
  margin: 0 !important;
242
  padding: 0 !important;
@@ -298,7 +348,6 @@ class ScreenshotService {
298
  z-index: 1 !important;
299
  }
300
 
301
- /* 隐藏所有滚动条 */
302
  html::-webkit-scrollbar,
303
  body::-webkit-scrollbar,
304
  *::-webkit-scrollbar {
@@ -309,7 +358,6 @@ class ScreenshotService {
309
 
310
  html { scrollbar-width: none !important; }
311
 
312
- /* 禁用用户交互 */
313
  * {
314
  -webkit-user-select: none !important;
315
  -moz-user-select: none !important;
@@ -332,47 +380,39 @@ class ScreenshotService {
332
  throw new Error('浏览器初始化失败');
333
  }
334
 
335
- // 从HTML中提取PPT的精确尺寸
336
  const dimensions = this.extractPPTDimensions(htmlContent);
337
  console.log(`📐 检测到PPT尺寸: ${dimensions.width}x${dimensions.height}`);
338
 
339
- // 优化HTML内容以确保精确截图
340
  const optimizedHtml = this.optimizeHtmlForScreenshot(htmlContent, dimensions.width, dimensions.height);
341
 
342
- // 创建新页面
343
  let page = null;
344
  try {
345
  page = await browser.newPage();
346
  console.log('📄 创建新页面成功');
347
 
348
- // 设置页面超时
349
- page.setDefaultTimeout(20000); // 减少超时时间
350
  page.setDefaultNavigationTimeout(20000);
351
 
352
- // 设置精确的viewport尺寸
353
  await page.setViewport({
354
  width: dimensions.width,
355
  height: dimensions.height,
356
- deviceScaleFactor: 1, // 固定为1避免缩放问题
357
  });
358
  console.log(`🖥️ 设置viewport: ${dimensions.width}x${dimensions.height}`);
359
 
360
- // 设置页面内容
361
  await page.setContent(optimizedHtml, {
362
  waitUntil: ['load', 'domcontentloaded'],
363
  timeout: 20000
364
  });
365
  console.log('📝 页面内容设置完成');
366
 
367
- // 等待页面完全渲染
368
- await page.waitForTimeout(1000); // 减少等待时间
369
  console.log('⏱️ 等待渲染完成');
370
 
371
- // 执行截图,使用精确的剪裁区域
372
  console.log('📸 开始执行截图...');
373
  const screenshot = await page.screenshot({
374
  type: 'jpeg',
375
- quality: 85, // 稍微降低质量提高速度
376
  clip: {
377
  x: 0,
378
  y: 0,
@@ -400,7 +440,6 @@ class ScreenshotService {
400
  } catch (error) {
401
  console.error(`❌ Puppeteer截图生成失败 (尝试 ${retryCount + 1}):`, error.message);
402
 
403
- // 如果是目标关闭错误或连接断开,重置浏览器
404
  if (error.message.includes('Target closed') ||
405
  error.message.includes('Connection closed') ||
406
  error.message.includes('Protocol error')) {
@@ -409,7 +448,6 @@ class ScreenshotService {
409
  this.isClosing = false;
410
  }
411
 
412
- // 如果还有重试机会,进行重试
413
  if (retryCount < this.maxRetries) {
414
  const waitTime = (retryCount + 1) * 2;
415
  console.log(`⏳ 等待 ${waitTime} 秒后重试...`);
@@ -421,47 +459,133 @@ class ScreenshotService {
421
  }
422
  }
423
 
424
- async generateScreenshot(htmlContent, options = {}) {
425
- console.log('🎯 开始生成截图...');
426
-
427
  try {
428
- // 首先尝试使用Puppeteer
429
- console.log('🚀 尝试使用Puppeteer生成截图');
430
- const screenshot = await this.generateScreenshotWithPuppeteer(htmlContent, options);
431
- console.log('✅ Puppeteer截图生成成功');
432
- return screenshot;
433
- } catch (puppeteerError) {
434
- console.warn('⚠️ Puppeteer截图失败,使用fallback方法:', puppeteerError.message);
435
 
436
- // 如果Puppeteer失败,生成fallback图片
 
 
 
 
437
  const dimensions = this.extractPPTDimensions(htmlContent);
438
- const fallbackImage = this.generateFallbackImage(
439
- dimensions.width,
440
- dimensions.height,
441
- 'Puppeteer不可用,显示占位图'
442
- );
 
 
 
 
 
 
 
 
443
 
444
- console.log(`📋 生成fallback图片,尺寸: ${dimensions.width}x${dimensions.height}`);
445
- return fallbackImage;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
446
  }
447
  }
448
 
449
- // 兼容旧方法名
450
  async captureScreenshot(htmlContent, width, height, options = {}) {
451
  console.log('使用兼容方法captureScreenshot,建议使用generateScreenshot');
452
  return this.generateScreenshot(htmlContent, options);
453
  }
454
 
455
- // 清理资源
456
  async cleanup() {
457
- await this.closeBrowser();
 
 
 
458
  }
459
  }
460
 
461
- // 创建单例实例
462
  const screenshotService = new ScreenshotService();
463
 
464
- // 进程退出时清理资源
465
  process.on('exit', async () => {
466
  await screenshotService.cleanup();
467
  });
 
1
  import puppeteer from 'puppeteer';
2
+ // 添加Playwright作为备用截图引擎
3
+ import { chromium } from 'playwright';
4
 
5
  class ScreenshotService {
6
  constructor() {
7
  this.browser = null;
8
+ this.playwrightBrowser = null; // 添加Playwright浏览器实例
9
  this.isInitialized = false;
10
  this.maxRetries = 2;
11
  this.browserLaunchRetries = 0;
12
  this.maxBrowserLaunchRetries = 2;
13
  this.isClosing = false;
14
  this.isHuggingFaceSpace = process.env.SPACE_ID || process.env.HF_SPACE_ID;
15
+ this.preferredEngine = 'puppeteer'; // 优先使用的截图引擎
16
  }
17
 
18
  async initBrowser() {
 
19
  if (this.isClosing) {
20
  console.log('浏览器正在关闭中,等待重新初始化...');
21
  this.browser = null;
 
28
 
29
  const launchOptions = {
30
  headless: 'new',
31
+ timeout: 60000,
32
  protocolTimeout: 60000,
33
  args: [
34
  '--no-sandbox',
 
58
  '--disable-hang-monitor',
59
  '--disable-prompt-on-repost',
60
  '--memory-pressure-off',
61
+ '--max_old_space_size=1024',
 
62
  '--disable-background-media-suspend',
63
  '--disable-backgrounding-occluded-windows',
64
  '--disable-renderer-backgrounding',
 
78
  ]
79
  };
80
 
 
81
  if (this.isHuggingFaceSpace) {
82
  console.log('检测到Hugging Face Space环境,使用单进程模式');
83
  launchOptions.args.push(
 
88
  );
89
  }
90
 
 
91
  try {
92
  const chromePath = process.env.CHROME_BIN ||
93
  process.env.GOOGLE_CHROME_BIN ||
 
105
  this.browser = await puppeteer.launch(launchOptions);
106
  console.log('✅ Puppeteer浏览器初始化成功');
107
 
 
108
  this.browser.on('disconnected', () => {
109
  console.log('Puppeteer浏览器连接断开');
110
  this.browser = null;
 
118
 
119
  if (this.browserLaunchRetries <= this.maxBrowserLaunchRetries) {
120
  console.log(`尝试重新初始化浏览器 (${this.browserLaunchRetries}/${this.maxBrowserLaunchRetries})`);
121
+ await this.delay(3000);
122
  return this.initBrowser();
123
  } else {
 
124
  console.warn('⚠️ Puppeteer初始化完全失败,将使用fallback方法');
125
  return null;
126
  }
127
  }
128
  }
129
 
 
130
  if (this.browser) {
131
  try {
132
  await this.browser.version();
 
141
  return this.browser;
142
  }
143
 
144
+ async initPlaywrightBrowser() {
145
+ if (this.playwrightBrowser) {
146
+ try {
147
+ const context = await this.playwrightBrowser.newContext();
148
+ await context.close();
149
+ return this.playwrightBrowser;
150
+ } catch (error) {
151
+ console.warn('Playwright浏览器连接已断开,重新初始化');
152
+ this.playwrightBrowser = null;
153
+ }
154
+ }
155
+
156
+ try {
157
+ console.log('初始化Playwright浏览器...');
158
+
159
+ const launchOptions = {
160
+ headless: true,
161
+ timeout: 30000,
162
+ args: [
163
+ '--no-sandbox',
164
+ '--disable-setuid-sandbox',
165
+ '--disable-dev-shm-usage',
166
+ '--disable-gpu',
167
+ '--disable-extensions',
168
+ '--no-first-run',
169
+ '--disable-background-timer-throttling',
170
+ '--disable-renderer-backgrounding',
171
+ '--disable-backgrounding-occluded-windows'
172
+ ]
173
+ };
174
+
175
+ if (this.isHuggingFaceSpace) {
176
+ console.log('检测到Hugging Face Space环境,使用Playwright优化配置');
177
+ launchOptions.args.push(
178
+ '--single-process',
179
+ '--no-zygote',
180
+ '--disable-web-security'
181
+ );
182
+ }
183
+
184
+ this.playwrightBrowser = await chromium.launch(launchOptions);
185
+ console.log('✅ Playwright浏览器初始化成功');
186
+
187
+ return this.playwrightBrowser;
188
+ } catch (error) {
189
+ console.error('❌ Playwright浏览器初始化失败:', error.message);
190
+ return null;
191
+ }
192
+ }
193
+
194
  async closeBrowser() {
195
  if (this.browser && !this.isClosing) {
196
  try {
 
207
  }
208
  }
209
 
210
+ async closePlaywrightBrowser() {
211
+ if (this.playwrightBrowser) {
212
+ try {
213
+ console.log('正在关闭Playwright浏览器...');
214
+ await this.playwrightBrowser.close();
215
+ console.log('Playwright浏览器已关闭');
216
+ } catch (error) {
217
+ console.warn('关闭Playwright浏览器时出错:', error.message);
218
+ } finally {
219
+ this.playwrightBrowser = null;
220
+ }
221
+ }
222
+ }
223
+
224
  delay(ms) {
225
  return new Promise(resolve => setTimeout(resolve, ms));
226
  }
227
 
 
228
  extractPPTDimensions(htmlContent) {
229
  try {
 
230
  const dimensionMatch = htmlContent.match(/window\.PPT_DIMENSIONS\s*=\s*{\s*width:\s*(\d+),\s*height:\s*(\d+)\s*}/);
231
  if (dimensionMatch) {
232
  return {
 
235
  };
236
  }
237
 
 
238
  const viewportMatch = htmlContent.match(/width=(\d+),\s*height=(\d+)/);
239
  if (viewportMatch) {
240
  return {
 
243
  };
244
  }
245
 
 
246
  return { width: 960, height: 720 };
247
  } catch (error) {
248
  console.warn('提取PPT尺寸失败,使用默认尺寸:', error.message);
 
250
  }
251
  }
252
 
 
253
  generateFallbackImage(width, height, message = 'Screenshot not available') {
254
  console.log(`🔄 生成fallback图片: ${width}x${height}`);
255
 
 
256
  const svg = `
257
  <svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg">
258
  <rect width="100%" height="100%" fill="#f8f9fa"/>
 
276
  </svg>
277
  `;
278
 
 
279
  return Buffer.from(svg, 'utf8');
280
  }
281
 
 
282
  optimizeHtmlForScreenshot(htmlContent, targetWidth, targetHeight) {
283
  console.log(`优化HTML for精确截图, 目标尺寸: ${targetWidth}x${targetHeight}`);
284
 
 
285
  const optimizedHtml = htmlContent.replace(
286
  /(<head[^>]*>)/i,
287
  `$1
288
  <meta name="screenshot-mode" content="true">
289
  <style id="screenshot-precise-control">
 
290
  *, *::before, *::after {
291
  margin: 0 !important;
292
  padding: 0 !important;
 
348
  z-index: 1 !important;
349
  }
350
 
 
351
  html::-webkit-scrollbar,
352
  body::-webkit-scrollbar,
353
  *::-webkit-scrollbar {
 
358
 
359
  html { scrollbar-width: none !important; }
360
 
 
361
  * {
362
  -webkit-user-select: none !important;
363
  -moz-user-select: none !important;
 
380
  throw new Error('浏览器初始化失败');
381
  }
382
 
 
383
  const dimensions = this.extractPPTDimensions(htmlContent);
384
  console.log(`📐 检测到PPT尺寸: ${dimensions.width}x${dimensions.height}`);
385
 
 
386
  const optimizedHtml = this.optimizeHtmlForScreenshot(htmlContent, dimensions.width, dimensions.height);
387
 
 
388
  let page = null;
389
  try {
390
  page = await browser.newPage();
391
  console.log('📄 创建新页面成功');
392
 
393
+ page.setDefaultTimeout(20000);
 
394
  page.setDefaultNavigationTimeout(20000);
395
 
 
396
  await page.setViewport({
397
  width: dimensions.width,
398
  height: dimensions.height,
399
+ deviceScaleFactor: 1,
400
  });
401
  console.log(`🖥️ 设置viewport: ${dimensions.width}x${dimensions.height}`);
402
 
 
403
  await page.setContent(optimizedHtml, {
404
  waitUntil: ['load', 'domcontentloaded'],
405
  timeout: 20000
406
  });
407
  console.log('📝 页面内容设置完成');
408
 
409
+ await page.waitForTimeout(1000);
 
410
  console.log('⏱️ 等待渲染完成');
411
 
 
412
  console.log('📸 开始执行截图...');
413
  const screenshot = await page.screenshot({
414
  type: 'jpeg',
415
+ quality: 85,
416
  clip: {
417
  x: 0,
418
  y: 0,
 
440
  } catch (error) {
441
  console.error(`❌ Puppeteer截图生成失败 (尝试 ${retryCount + 1}):`, error.message);
442
 
 
443
  if (error.message.includes('Target closed') ||
444
  error.message.includes('Connection closed') ||
445
  error.message.includes('Protocol error')) {
 
448
  this.isClosing = false;
449
  }
450
 
 
451
  if (retryCount < this.maxRetries) {
452
  const waitTime = (retryCount + 1) * 2;
453
  console.log(`⏳ 等待 ${waitTime} 秒后重试...`);
 
459
  }
460
  }
461
 
462
+ async generateScreenshotWithPlaywright(htmlContent, options = {}) {
 
 
463
  try {
464
+ console.log('🎭 开始Playwright截图生成...');
 
 
 
 
 
 
465
 
466
+ const browser = await this.initPlaywrightBrowser();
467
+ if (!browser) {
468
+ throw new Error('Playwright浏览器初始化失败');
469
+ }
470
+
471
  const dimensions = this.extractPPTDimensions(htmlContent);
472
+ console.log(`📐 Playwright检测到PPT尺寸: ${dimensions.width}x${dimensions.height}`);
473
+
474
+ const context = await browser.newContext({
475
+ viewport: {
476
+ width: dimensions.width,
477
+ height: dimensions.height
478
+ },
479
+ deviceScaleFactor: 1,
480
+ hasTouch: false,
481
+ isMobile: false
482
+ });
483
+
484
+ const page = await context.newPage();
485
 
486
+ await page.setContent(htmlContent, {
487
+ waitUntil: 'domcontentloaded',
488
+ timeout: 15000
489
+ });
490
+
491
+ await page.waitForTimeout(800);
492
+
493
+ const screenshot = await page.screenshot({
494
+ type: 'jpeg',
495
+ quality: 90,
496
+ clip: {
497
+ x: 0,
498
+ y: 0,
499
+ width: dimensions.width,
500
+ height: dimensions.height
501
+ },
502
+ animations: 'disabled'
503
+ });
504
+
505
+ await context.close();
506
+
507
+ console.log(`✅ Playwright截图成功生成,尺寸: ${dimensions.width}x${dimensions.height}, 数据大小: ${screenshot.length} 字节`);
508
+ return screenshot;
509
+
510
+ } catch (error) {
511
+ console.error('❌ Playwright截图生成失败:', error.message);
512
+ throw error;
513
+ }
514
+ }
515
+
516
+ async generateScreenshot(htmlContent, options = {}) {
517
+ console.log('🎯 开始生成截图...');
518
+
519
+ if (this.preferredEngine === 'puppeteer') {
520
+ try {
521
+ console.log('🚀 尝试使用Puppeteer生成截图');
522
+ const screenshot = await this.generateScreenshotWithPuppeteer(htmlContent, options);
523
+ console.log('✅ Puppeteer截图生成成功');
524
+ return screenshot;
525
+ } catch (puppeteerError) {
526
+ console.warn('⚠️ Puppeteer截图失败,尝试Playwright:', puppeteerError.message);
527
+
528
+ try {
529
+ const screenshot = await this.generateScreenshotWithPlaywright(htmlContent, options);
530
+ console.log('✅ Playwright截图生成成功');
531
+ this.preferredEngine = 'playwright';
532
+ return screenshot;
533
+ } catch (playwrightError) {
534
+ console.warn('⚠️ Playwright截图也失败,使用fallback方法:', playwrightError.message);
535
+ }
536
+ }
537
+ } else {
538
+ try {
539
+ console.log('🎭 尝试使用Playwright生成截图');
540
+ const screenshot = await this.generateScreenshotWithPlaywright(htmlContent, options);
541
+ console.log('✅ Playwright截图生成成功');
542
+ return screenshot;
543
+ } catch (playwrightError) {
544
+ console.warn('⚠️ Playwright截图失败,尝试Puppeteer:', playwrightError.message);
545
+
546
+ try {
547
+ const screenshot = await this.generateScreenshotWithPuppeteer(htmlContent, options);
548
+ console.log('✅ Puppeteer截图生成成功');
549
+ return screenshot;
550
+ } catch (puppeteerError) {
551
+ console.warn('⚠️ Puppeteer截图也失败,使用fallback方法:', puppeteerError.message);
552
+ }
553
+ }
554
+ }
555
+
556
+ const dimensions = this.extractPPTDimensions(htmlContent);
557
+ const fallbackImage = this.generateFallbackImage(
558
+ dimensions.width,
559
+ dimensions.height,
560
+ '截图引擎不可用,显示占位图'
561
+ );
562
+
563
+ console.log(`📋 生成fallback图片,尺寸: ${dimensions.width}x${dimensions.height}`);
564
+ return fallbackImage;
565
+ }
566
+
567
+ setPreferredEngine(engine) {
568
+ if (['puppeteer', 'playwright'].includes(engine)) {
569
+ this.preferredEngine = engine;
570
+ console.log(`截图引擎偏好设置为: ${engine}`);
571
  }
572
  }
573
 
 
574
  async captureScreenshot(htmlContent, width, height, options = {}) {
575
  console.log('使用兼容方法captureScreenshot,建议使用generateScreenshot');
576
  return this.generateScreenshot(htmlContent, options);
577
  }
578
 
 
579
  async cleanup() {
580
+ await Promise.all([
581
+ this.closeBrowser(),
582
+ this.closePlaywrightBrowser()
583
+ ]);
584
  }
585
  }
586
 
 
587
  const screenshotService = new ScreenshotService();
588
 
 
589
  process.on('exit', async () => {
590
  await screenshotService.cleanup();
591
  });