mirror of
https://github.com/hydralauncher/hydra.git
synced 2026-01-11 13:56:16 +00:00
180 lines
5.1 KiB
TypeScript
180 lines
5.1 KiB
TypeScript
import { desktopCapturer, nativeImage, app } from "electron";
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import { logger } from "./logger";
|
|
import { screenshotsPath } from "@main/constants";
|
|
|
|
export class ScreenshotService {
|
|
private static readonly SCREENSHOT_QUALITY = 80;
|
|
private static readonly SCREENSHOT_FORMAT = "jpeg";
|
|
private static readonly MAX_WIDTH = 1280;
|
|
private static readonly MAX_HEIGHT = 720;
|
|
|
|
private static compressImage(
|
|
image: Electron.NativeImage
|
|
): Electron.NativeImage {
|
|
const size = image.getSize();
|
|
|
|
let newWidth = size.width;
|
|
let newHeight = size.height;
|
|
|
|
if (newWidth > this.MAX_WIDTH || newHeight > this.MAX_HEIGHT) {
|
|
const aspectRatio = newWidth / newHeight;
|
|
|
|
if (newWidth > newHeight) {
|
|
newWidth = this.MAX_WIDTH;
|
|
newHeight = Math.round(newWidth / aspectRatio);
|
|
} else {
|
|
newHeight = this.MAX_HEIGHT;
|
|
newWidth = Math.round(newHeight * aspectRatio);
|
|
}
|
|
}
|
|
|
|
if (newWidth !== size.width || newHeight !== size.height) {
|
|
return image.resize({ width: newWidth, height: newHeight });
|
|
}
|
|
|
|
return image;
|
|
}
|
|
|
|
public static async captureDesktopScreenshot(
|
|
gameTitle?: string,
|
|
achievementName?: string
|
|
): Promise<string> {
|
|
try {
|
|
const sources = await desktopCapturer.getSources({
|
|
types: ["screen"],
|
|
thumbnailSize: { width: 1920, height: 1080 },
|
|
});
|
|
|
|
if (sources.length === 0) {
|
|
throw new Error("No desktop sources available for screenshot");
|
|
}
|
|
|
|
console.log("sources", sources);
|
|
|
|
const primaryScreen = sources[0];
|
|
|
|
const originalImage = nativeImage.createFromDataURL(
|
|
primaryScreen.thumbnail.toDataURL()
|
|
);
|
|
|
|
const compressedImage = this.compressImage(originalImage);
|
|
|
|
let finalDir = screenshotsPath;
|
|
let filename: string;
|
|
|
|
if (gameTitle && achievementName) {
|
|
const sanitizedGameTitle = gameTitle.replaceAll(/[<>:"/\\|?*]/g, "_");
|
|
const gameDir = path.join(screenshotsPath, sanitizedGameTitle);
|
|
finalDir = gameDir;
|
|
|
|
const sanitizedAchievementName = achievementName.replaceAll(
|
|
/[<>:"/\\|?*]/g,
|
|
"_"
|
|
);
|
|
filename = `${sanitizedAchievementName}.${this.SCREENSHOT_FORMAT}`;
|
|
} else {
|
|
const timestamp = Date.now();
|
|
filename = `achievement_screenshot_${timestamp}.${this.SCREENSHOT_FORMAT}`;
|
|
}
|
|
|
|
if (!fs.existsSync(finalDir)) {
|
|
fs.mkdirSync(finalDir, { recursive: true });
|
|
}
|
|
|
|
const screenshotPath = path.join(finalDir, filename);
|
|
|
|
const jpegBuffer = compressedImage.toJPEG(this.SCREENSHOT_QUALITY);
|
|
fs.writeFileSync(screenshotPath, jpegBuffer);
|
|
|
|
logger.log(`Compressed screenshot saved to: ${screenshotPath}`);
|
|
return screenshotPath;
|
|
} catch (error) {
|
|
logger.error("Failed to capture desktop screenshot:", error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
public static async cleanupOldScreenshots(): Promise<void> {
|
|
try {
|
|
const userDataPath = app.getPath("userData");
|
|
const screenshotsDir = path.join(userDataPath, "screenshots");
|
|
|
|
if (!fs.existsSync(screenshotsDir)) {
|
|
return;
|
|
}
|
|
|
|
const getAllFiles = (
|
|
dir: string
|
|
): Array<{ name: string; path: string; mtime: Date }> => {
|
|
const files: Array<{ name: string; path: string; mtime: Date }> = [];
|
|
|
|
const items = fs.readdirSync(dir);
|
|
for (const item of items) {
|
|
const itemPath = path.join(dir, item);
|
|
const stat = fs.statSync(itemPath);
|
|
|
|
if (stat.isDirectory()) {
|
|
files.push(...getAllFiles(itemPath));
|
|
} else if (item.endsWith(`.${this.SCREENSHOT_FORMAT}`)) {
|
|
files.push({
|
|
name: item,
|
|
path: itemPath,
|
|
mtime: stat.mtime,
|
|
});
|
|
}
|
|
}
|
|
|
|
return files;
|
|
};
|
|
|
|
const allFiles = getAllFiles(screenshotsDir).sort(
|
|
(a, b) => b.mtime.getTime() - a.mtime.getTime()
|
|
);
|
|
|
|
const filesToDelete = allFiles.slice(50);
|
|
|
|
for (const file of filesToDelete) {
|
|
try {
|
|
fs.unlinkSync(file.path);
|
|
logger.log(`Cleaned up old screenshot: ${file.name}`);
|
|
} catch (error) {
|
|
logger.error(`Failed to delete screenshot ${file.name}:`, error);
|
|
}
|
|
}
|
|
|
|
const cleanupEmptyDirs = (dir: string) => {
|
|
if (dir === screenshotsDir) return;
|
|
|
|
try {
|
|
const items = fs.readdirSync(dir);
|
|
if (items.length === 0) {
|
|
fs.rmdirSync(dir);
|
|
logger.log(`Cleaned up empty directory: ${dir}`);
|
|
}
|
|
} catch (error) {
|
|
logger.error(`Failed to read directory ${dir}:`, error);
|
|
}
|
|
};
|
|
|
|
const gameDirectories = fs
|
|
.readdirSync(screenshotsDir)
|
|
.map((item) => path.join(screenshotsDir, item))
|
|
.filter((itemPath) => {
|
|
try {
|
|
return fs.statSync(itemPath).isDirectory();
|
|
} catch {
|
|
return false;
|
|
}
|
|
});
|
|
|
|
for (const gameDir of gameDirectories) {
|
|
cleanupEmptyDirs(gameDir);
|
|
}
|
|
} catch (error) {
|
|
logger.error("Failed to cleanup old screenshots:", error);
|
|
}
|
|
}
|
|
}
|