import puppeteer from 'puppeteer'; import playwright from 'playwright'; class ScreenshotService { constructor() { this.puppeteerBrowser = null; this.playwrightBrowser = null; this.isInitializing = false; this.puppeteerReady = false; this.playwrightReady = false; console.log('Screenshot service initialized'); } // Initialize Puppeteer browser async initPuppeteer() { if (this.puppeteerBrowser || this.isInitializing) return; this.isInitializing = true; try { console.log('๐Ÿš€ Starting Puppeteer browser...'); const launchOptions = { headless: 'new', args: [ '--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage', '--disable-gpu', '--disable-extensions', '--disable-background-timer-throttling', '--disable-backgrounding-occluded-windows', '--disable-renderer-backgrounding', '--no-first-run', '--no-default-browser-check', '--disable-default-apps', '--disable-features=TranslateUI', '--disable-ipc-flooding-protection', '--memory-pressure-off', '--max_old_space_size=4096' ], timeout: 30000 }; // Hugging Face Spaces optimizations if (process.env.SPACE_ID) { launchOptions.args.push( '--single-process', '--disable-background-networking', '--disable-background-mode' ); } this.puppeteerBrowser = await puppeteer.launch(launchOptions); this.puppeteerReady = true; console.log('โœ… Puppeteer browser started successfully'); // Cleanup when process exits process.on('exit', () => this.cleanup()); process.on('SIGINT', () => this.cleanup()); process.on('SIGTERM', () => this.cleanup()); } catch (error) { console.error('โŒ Puppeteer initialization failed:', error.message); this.puppeteerReady = false; } finally { this.isInitializing = false; } } // Initialize Playwright browser (fallback) async initPlaywright() { if (this.playwrightBrowser) return; try { console.log('๐ŸŽญ Starting Playwright browser...'); this.playwrightBrowser = await playwright.chromium.launch({ headless: true, args: [ '--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage' ] }); this.playwrightReady = true; console.log('โœ… Playwright browser started successfully'); } catch (error) { console.error('โŒ Playwright initialization failed:', error.message); this.playwrightReady = false; } } // Generate screenshot async generateScreenshot(htmlContent, options = {}) { const { format = 'jpeg', quality = 90, width = 1000, height = 562, timeout = 15000 } = options; console.log(`๐Ÿ“ธ Generating screenshot: ${width}x${height}, ${format}@${quality}%`); // Try Puppeteer first if (!this.puppeteerReady) { await this.initPuppeteer(); } if (this.puppeteerReady) { try { return await this.generateWithPuppeteer(htmlContent, { format, quality, width, height, timeout }); } catch (error) { console.warn('Puppeteer screenshot failed, trying Playwright:', error.message); } } // Fallback to Playwright if (!this.playwrightReady) { await this.initPlaywright(); } if (this.playwrightReady) { try { return await this.generateWithPlaywright(htmlContent, { format, quality, width, height, timeout }); } catch (error) { console.warn('Playwright screenshot failed:', error.message); } } // Final fallback: generate SVG console.warn('All screenshot methods failed, generating fallback SVG'); return this.generateFallbackImage(width, height, 'Screenshot Service', 'Browser unavailable'); } // Generate screenshot using Puppeteer async generateWithPuppeteer(htmlContent, options) { const { format, quality, width, height, timeout } = options; let page = null; try { page = await this.puppeteerBrowser.newPage(); // Set viewport await page.setViewport({ width, height }); // Set content await page.setContent(htmlContent, { waitUntil: 'networkidle0', timeout: timeout }); // Wait for fonts await page.evaluate(() => { return document.fonts ? document.fonts.ready : Promise.resolve(); }); // Additional wait for rendering await page.waitForTimeout(300); // Take screenshot const screenshotOptions = { type: format, quality: format === 'jpeg' ? quality : undefined, fullPage: false, clip: { x: 0, y: 0, width, height } }; const screenshot = await page.screenshot(screenshotOptions); console.log(`โœ… Puppeteer screenshot generated: ${screenshot.length} bytes`); return screenshot; } finally { if (page) { await page.close().catch(console.error); } } } // Generate screenshot using Playwright async generateWithPlaywright(htmlContent, options) { const { format, quality, width, height, timeout } = options; let page = null; try { page = await this.playwrightBrowser.newPage(); // Set viewport await page.setViewportSize({ width, height }); // Set content await page.setContent(htmlContent, { waitUntil: 'networkidle', timeout: timeout }); // Wait for fonts await page.evaluate(() => { return document.fonts ? document.fonts.ready : Promise.resolve(); }); // Take screenshot const screenshotOptions = { type: format, quality: format === 'jpeg' ? quality : undefined, fullPage: false, clip: { x: 0, y: 0, width, height } }; const screenshot = await page.screenshot(screenshotOptions); console.log(`โœ… Playwright screenshot generated: ${screenshot.length} bytes`); return screenshot; } finally { if (page) { await page.close().catch(console.error); } } } // Generate fallback SVG image generateFallbackImage(width = 1000, height = 562, title = 'PPT Screenshot', subtitle = '', message = '') { const svg = ` ${title} ${subtitle ? ` ${subtitle} ` : ''} ${message ? ` ${message} ` : ''} PPT Screenshot Service โ€ข ${new Date().toLocaleString('zh-CN')} Size: ${width} ร— ${height} `; return Buffer.from(svg, 'utf-8'); } // Get service status getStatus() { return { puppeteerReady: this.puppeteerReady, playwrightReady: this.playwrightReady, environment: process.env.NODE_ENV || 'development', isHuggingFace: !!process.env.SPACE_ID }; } // Cleanup resources async cleanup() { console.log('๐Ÿงน Cleaning up screenshot service...'); if (this.puppeteerBrowser) { try { await this.puppeteerBrowser.close(); this.puppeteerBrowser = null; this.puppeteerReady = false; console.log('โœ… Puppeteer browser closed'); } catch (error) { console.error('Error closing Puppeteer browser:', error); } } if (this.playwrightBrowser) { try { await this.playwrightBrowser.close(); this.playwrightBrowser = null; this.playwrightReady = false; console.log('โœ… Playwright browser closed'); } catch (error) { console.error('Error closing Playwright browser:', error); } } } } export default new ScreenshotService();