web_ppt / backend /src /services /screenshotService.js
CatPtain's picture
Upload 11 files
0afb612 verified
raw
history blame
11 kB
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 = `<?xml version="1.0" encoding="UTF-8"?>
<svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#f8f9fa;stop-opacity:1" />
<stop offset="100%" style="stop-color:#e9ecef;stop-opacity:1" />
</linearGradient>
<pattern id="dots" patternUnits="userSpaceOnUse" width="20" height="20">
<circle cx="2" cy="2" r="1" fill="#dee2e6" opacity="0.5"/>
</pattern>
</defs>
<!-- Background -->
<rect width="100%" height="100%" fill="url(#bg)"/>
<rect width="100%" height="100%" fill="url(#dots)"/>
<!-- Border -->
<rect x="20" y="20" width="${width-40}" height="${height-40}"
fill="none" stroke="#dee2e6" stroke-width="3"
stroke-dasharray="15,10" rx="10"/>
<!-- Icon -->
<g transform="translate(${width/2}, ${height*0.25})">
<circle cx="0" cy="0" r="30" fill="#6c757d" opacity="0.3"/>
<rect x="-15" y="-10" width="30" height="20" rx="3" fill="#6c757d"/>
<rect x="-12" y="-7" width="24" height="3" fill="white"/>
<rect x="-12" y="-2" width="24" height="3" fill="white"/>
<rect x="-12" y="3" width="16" height="3" fill="white"/>
</g>
<!-- Title -->
<text x="${width/2}" y="${height*0.45}"
text-anchor="middle"
font-family="Microsoft YaHei, PingFang SC, Hiragino Sans GB, Source Han Sans SC, Noto Sans SC, WenQuanYi Micro Hei, SimHei, SimSun, Arial, sans-serif"
font-size="28"
font-weight="bold"
fill="#495057">${title}</text>
${subtitle ? `
<!-- Subtitle -->
<text x="${width/2}" y="${height*0.55}"
text-anchor="middle"
font-family="Microsoft YaHei, PingFang SC, Hiragino Sans GB, Source Han Sans SC, Noto Sans SC, WenQuanYi Micro Hei, SimHei, SimSun, Arial, sans-serif"
font-size="18"
fill="#6c757d">${subtitle}</text>
` : ''}
${message ? `
<!-- Message -->
<text x="${width/2}" y="${height*0.65}"
text-anchor="middle"
font-family="Microsoft YaHei, PingFang SC, Hiragino Sans GB, Source Han Sans SC, Noto Sans SC, WenQuanYi Micro Hei, SimHei, SimSun, Arial, sans-serif"
font-size="14"
fill="#adb5bd">${message}</text>
` : ''}
<!-- Footer -->
<text x="${width/2}" y="${height*0.85}"
text-anchor="middle"
font-family="Microsoft YaHei, PingFang SC, Hiragino Sans GB, Source Han Sans SC, Noto Sans SC, WenQuanYi Micro Hei, SimHei, SimSun, Arial, sans-serif"
font-size="12"
fill="#ced4da">PPT Screenshot Service β€’ ${new Date().toLocaleString('zh-CN')}</text>
<!-- Dimensions info -->
<text x="${width/2}" y="${height*0.92}"
text-anchor="middle"
font-family="Microsoft YaHei, PingFang SC, Hiragino Sans GB, Source Han Sans SC, Noto Sans SC, WenQuanYi Micro Hei, SimHei, SimSun, Arial, sans-serif"
font-size="10"
fill="#ced4da">Size: ${width} Γ— ${height}</text>
</svg>`;
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();