screencapturekit
Version:
A nodejs wrapper over a swift CLI program which is a wrapper over ScreenCaptureKit module with HDR and microphone support
1 lines • 27.6 kB
Source Map (JSON)
{"version":3,"sources":["../src/index.ts","../src/utils/packagePaths.ts"],"sourcesContent":["import os from \"node:os\";\nimport path from \"node:path\";\nimport fs from \"node:fs\";\nimport { v4 as uuidv4 } from \"uuid\";\nimport * as macosVersion from \"macos-version\";\nimport { execa, type ExecaChildProcess } from \"execa\";\nimport { resolvePackagePath } from \"./utils/packagePaths.js\";\nconst BIN = resolvePackagePath(\"./screencapturekit\"); // Simplified path\n\n/**\n * Generates a random identifier composed of alphanumeric characters.\n * @returns {string} A random identifier as a string.\n * @private\n */\nconst getRandomId = () => Math.random().toString(36).slice(2, 15);\n\n/**\n * Checks if the system supports HEVC (H.265) hardware encoding.\n * @returns {boolean} True if the system supports HEVC hardware encoding, false otherwise.\n * @private\n */\nconst supportsHevcHardwareEncoding = (() => {\n const cpuModel = os.cpus()[0].model;\n\n // All Apple silicon Macs support HEVC hardware encoding.\n if (cpuModel.startsWith(\"Apple \")) {\n // Source string example: 'Apple M1'\n return true;\n }\n\n // Get the Intel Core generation, the `4` in `Intel(R) Core(TM) i7-4850HQ CPU @ 2.30GHz`\n // More info: https://www.intel.com/content/www/us/en/processors/processor-numbers.html\n // Example strings:\n // - `Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz`\n // - `Intel(R) Core(TM) i7-4850HQ CPU @ 2.30GHz`\n const result = /Intel.*Core.*i\\d+-(\\d)/.exec(cpuModel);\n\n // Intel Core generation 6 or higher supports HEVC hardware encoding\n return result && Number.parseInt(result[1], 10) >= 6;\n})();\n\n/**\n * Checks if the system supports HDR capture.\n * @returns {boolean} True if the system supports HDR capture (macOS 13.0+), false otherwise.\n * @private\n */\nconst supportsHDR = (() => {\n return macosVersion.isMacOSVersionGreaterThanOrEqualTo(\"13.0\"); // HDR requires macOS 13.0+ (Ventura)\n})();\n\n/**\n * Checks if the system supports the direct recording API.\n * @returns {boolean} True if the system supports direct recording API (macOS 15.0+), false otherwise.\n * @private\n */\nconst supportsDirectRecordingAPI = (() => {\n return macosVersion.isMacOSVersionGreaterThanOrEqualTo(\"15.0\"); // Direct Recording API requires macOS 15.0+\n})();\n\n/**\n * Checks if the system supports microphone capture.\n * @returns {boolean} True if the system supports microphone capture (macOS 15.0+), false otherwise.\n * @private\n */\nconst supportsMicrophoneCapture = (() => {\n return macosVersion.isMacOSVersionGreaterThanOrEqualTo(\"15.0\"); // Microphone support with SCStream requires macOS 15.0+\n})();\n\n/**\n * Interface defining a cropping area for recording.\n * @typedef {Object} CropArea\n * @property {number} x - The X position of the starting point of the area.\n * @property {number} y - The Y position of the starting point of the area.\n * @property {number} width - The width of the area to capture.\n * @property {number} height - The height of the area to capture.\n */\ntype CropArea = {\n x: number;\n y: number;\n width: number;\n height: number;\n};\n\nexport type { CropArea };\n\ntype Screen = {\n id: number;\n width: number;\n height: number;\n};\n\nexport type { Screen };\n\ntype AudioDevice = {\n id: string;\n name: string;\n manufacturer: string;\n};\n\nexport type { AudioDevice };\n\ntype MicrophoneDevice = AudioDevice;\n\nexport type { MicrophoneDevice };\n\n/**\n * Options for screen recording.\n * @typedef {Object} RecordingOptions\n * @property {number} fps - Frames per second.\n * @property {CropArea} [cropArea] - Area of the screen to capture.\n * @property {boolean} showCursor - Show the cursor in the recording.\n * @property {boolean} highlightClicks - Highlight mouse clicks.\n * @property {number} screenId - Identifier of the screen to capture.\n * @property {string} [audioDeviceId] - Identifier of the system audio device.\n * @property {string} [microphoneDeviceId] - Identifier of the microphone device.\n * @property {string} videoCodec - Video codec to use.\n * @property {boolean} [enableHDR] - Enable HDR recording (on macOS 13.0+).\n * @property {boolean} [recordToFile] - Use the direct recording API (on macOS 14.0+).\n * @property {boolean} [audioOnly] - Record audio only, will convert to mp3 after recording.\n */\ntype RecordingOptions = {\n fps: number;\n cropArea?: CropArea;\n showCursor: boolean;\n highlightClicks: boolean;\n screenId: number;\n audioDeviceId?: string;\n microphoneDeviceId?: string;\n videoCodec?: string;\n enableHDR?: boolean;\n recordToFile?: boolean;\n audioOnly?: boolean;\n};\n\nexport type { RecordingOptions };\n\n/**\n * Internal options for recording with ScreenCaptureKit.\n * @typedef {Object} RecordingOptionsForScreenCaptureKit\n * @property {string} destination - URL of the destination file.\n * @property {number} framesPerSecond - Frames per second.\n * @property {boolean} showCursor - Show the cursor in the recording.\n * @property {boolean} highlightClicks - Highlight mouse clicks.\n * @property {number} screenId - Identifier of the screen to capture.\n * @property {string} [audioDeviceId] - Identifier of the system audio device.\n * @property {string} [microphoneDeviceId] - Identifier of the microphone device.\n * @property {string} [videoCodec] - Video codec to use.\n * @property {Array} [cropRect] - Coordinates of the cropping area.\n * @property {boolean} [enableHDR] - Enable HDR recording.\n * @property {boolean} [useDirectRecordingAPI] - Use the direct recording API.\n * @private\n */\ntype RecordingOptionsForScreenCaptureKit = {\n destination: string;\n framesPerSecond: number;\n showCursor: boolean;\n highlightClicks: boolean;\n screenId: number;\n audioDeviceId?: string;\n microphoneDeviceId?: string;\n videoCodec?: string;\n cropRect?: [[x: number, y: number], [width: number, height: number]];\n enableHDR?: boolean;\n useDirectRecordingAPI?: boolean;\n};\n\nexport type { RecordingOptionsForScreenCaptureKit };\n\n/**\n * Main class for screen recording with ScreenCaptureKit.\n * Allows capturing the screen using Apple's native APIs.\n */\nexport class ScreenCaptureKit {\n /** Path to the output video file. */\n videoPath: string | null = null;\n /** The ongoing recording process. */\n recorder?: ExecaChildProcess;\n /** Unique identifier of the recording process. */\n processId: string | null = null;\n /** Options used for recording */\n private currentOptions?: Partial<RecordingOptions>;\n /** Path to the final processed video file */\n processedVideoPath: string | null = null;\n\n /**\n * Creates a new instance of ScreenCaptureKit.\n * Checks that the macOS version is compatible (10.13+).\n * @throws {Error} If the macOS version is not supported.\n */\n constructor() {\n macosVersion.assertMacOSVersionGreaterThanOrEqualTo(\"10.13\");\n }\n\n /**\n * Checks that recording has been started.\n * @throws {Error} If recording has not been started.\n * @private\n */\n throwIfNotStarted() {\n if (this.recorder === undefined) {\n throw new Error(\"Call `.startRecording()` first\");\n }\n }\n\n /**\n * Starts screen recording.\n * @param {Partial<RecordingOptions>} options - Recording options.\n * @param {number} [options.fps=30] - Frames per second.\n * @param {CropArea} [options.cropArea] - Area of the screen to capture.\n * @param {boolean} [options.showCursor=true] - Show the cursor.\n * @param {boolean} [options.highlightClicks=false] - Highlight mouse clicks.\n * @param {number} [options.screenId=0] - Screen ID to capture.\n * @param {string} [options.audioDeviceId] - System audio device ID.\n * @param {string} [options.microphoneDeviceId] - Microphone device ID.\n * @param {string} [options.videoCodec=\"h264\"] - Video codec to use.\n * @param {boolean} [options.enableHDR=false] - Enable HDR recording.\n * @param {boolean} [options.recordToFile=false] - Use the direct recording API.\n * @returns {Promise<void>} A promise that resolves when recording starts.\n * @throws {Error} If recording is already in progress or if the options are invalid.\n */\n async startRecording({\n fps = 30,\n cropArea = undefined,\n showCursor = true,\n highlightClicks = false,\n screenId = 0,\n audioDeviceId = undefined,\n microphoneDeviceId = undefined,\n videoCodec = \"h264\",\n enableHDR = false,\n recordToFile = false,\n audioOnly = false,\n }: Partial<RecordingOptions> = {}) {\n this.processId = getRandomId();\n // Stocke les options actuelles pour utilisation ultérieure\n this.currentOptions = {\n fps,\n cropArea,\n showCursor,\n highlightClicks,\n screenId,\n audioDeviceId,\n microphoneDeviceId,\n videoCodec,\n enableHDR,\n recordToFile,\n audioOnly,\n };\n \n return new Promise((resolve, reject) => {\n if (this.recorder !== undefined) {\n reject(new Error(\"Call `.stopRecording()` first\"));\n return;\n }\n\n this.videoPath = createTempFile({ extension: \"mp4\" });\n \n console.log(this.videoPath);\n const recorderOptions: RecordingOptionsForScreenCaptureKit = {\n destination: fileUrlFromPath(this.videoPath as string),\n framesPerSecond: fps,\n showCursor,\n highlightClicks,\n screenId,\n audioDeviceId,\n };\n\n if (highlightClicks === true) {\n showCursor = true;\n }\n\n if (\n typeof cropArea === \"object\" &&\n (typeof cropArea.x !== \"number\" ||\n typeof cropArea.y !== \"number\" ||\n typeof cropArea.width !== \"number\" ||\n typeof cropArea.height !== \"number\")\n ) {\n reject(new Error(\"Invalid `cropArea` option object\"));\n return;\n }\n\n if (videoCodec) {\n if (!videoCodecs.has(videoCodec)) {\n throw new Error(`Unsupported video codec specified: ${videoCodec}`);\n }\n\n recorderOptions.videoCodec = videoCodecs.get(videoCodec);\n }\n\n if (enableHDR) {\n if (!supportsHDR) {\n console.warn(\n \"HDR requested but not supported on this macOS version. Falling back to SDR.\"\n );\n } else {\n recorderOptions.enableHDR = true;\n }\n }\n\n if (microphoneDeviceId) {\n if (!supportsMicrophoneCapture) {\n console.warn(\n \"Microphone capture requested but requires macOS 15.0+. This feature will be ignored.\"\n );\n } else {\n recorderOptions.microphoneDeviceId = microphoneDeviceId;\n }\n }\n\n if (recordToFile) {\n if (!supportsDirectRecordingAPI) {\n console.warn(\n \"Direct recording API requested but requires macOS 15.0+. Falling back to manual recording.\"\n );\n } else {\n recorderOptions.useDirectRecordingAPI = true;\n }\n }\n\n if (cropArea) {\n recorderOptions.cropRect = [\n [cropArea.x, cropArea.y],\n [cropArea.width, cropArea.height],\n ];\n }\n\n const timeout = setTimeout(resolve, 1000);\n this.recorder = execa(BIN, [\"record\", JSON.stringify(recorderOptions)]);\n\n this.recorder?.catch((error) => {\n clearTimeout(timeout);\n delete this.recorder;\n reject(error);\n });\n\n this.recorder?.stdout?.setEncoding(\"utf8\");\n this.recorder?.stdout?.on(\"data\", (data) => {\n console.log(\"From swift executable: \", data);\n });\n });\n }\n\n /**\n * Stops the ongoing recording and processes the video to merge audio tracks if needed.\n * @returns {Promise<string|null>} A promise that resolves with the path to the processed video file.\n * @throws {Error} If recording has not been started.\n */\n async stopRecording() {\n this.throwIfNotStarted();\n console.log(\"Arrêt de l'enregistrement\");\n this.recorder?.kill();\n await this.recorder;\n console.log(\"Enregistrement arrêté\");\n this.recorder = undefined;\n\n if (!this.videoPath) {\n return null;\n }\n\n // Ajoutons un délai pour s'assurer que le fichier est complètement écrit\n await new Promise(resolve => setTimeout(resolve, 1000));\n \n let currentFile = this.videoPath;\n\n // Vérifier si le fichier existe et a une taille\n try {\n const stats = fs.statSync(currentFile);\n if (stats.size === 0) {\n console.error(\"Le fichier d'enregistrement est vide\");\n return null;\n }\n } catch (error) {\n console.error(\"Erreur lors de la vérification du fichier d'enregistrement:\", error);\n return null;\n }\n\n // Si nous avons plusieurs sources audio, nous devons les fusionner\n const hasMultipleAudioTracks = !!(\n this.currentOptions?.audioDeviceId && \n this.currentOptions?.microphoneDeviceId\n );\n\n if (hasMultipleAudioTracks) {\n try {\n console.log(\"Fusion des pistes audio avec ffmpeg\");\n this.processedVideoPath = createTempFile({ extension: \"mp4\" });\n \n // Vérifier la structure du fichier avec ffprobe\n const { stdout: probeOutput } = await execa(\"ffprobe\", [\n \"-v\", \"error\",\n \"-show_entries\", \"stream=index,codec_type\",\n \"-of\", \"json\",\n currentFile\n ]);\n \n const probeResult = JSON.parse(probeOutput);\n const streams = probeResult.streams || [];\n \n // Identifier les indices des flux audio et vidéo\n const audioStreams = streams\n .filter((stream: {codec_type: string; index: number}) => stream.codec_type === \"audio\")\n .map((stream: {index: number}) => stream.index);\n \n const videoStream = streams\n .find((stream: {codec_type: string; index: number}) => stream.codec_type === \"video\")?.index;\n \n if (audioStreams.length < 2 || videoStream === undefined) {\n console.log(\"Pas assez de pistes audio pour fusionner ou pas de piste vidéo\");\n } else {\n const systemAudioIndex = audioStreams[0];\n const microphoneIndex = audioStreams[1];\n \n const filterComplex = `[0:${systemAudioIndex}]volume=1[a1];[0:${microphoneIndex}]volume=3[a2];[a1][a2]amerge=inputs=2[aout]`;\n \n // Traitement vidéo\n await execa(\"ffmpeg\", [\n \"-i\", currentFile,\n \"-filter_complex\", filterComplex,\n \"-map\", \"[aout]\",\n \"-map\", `0:${videoStream}`,\n \"-c:v\", \"copy\",\n \"-c:a\", \"aac\",\n \"-b:a\", \"256k\",\n \"-ac\", \"2\",\n \"-y\",\n this.processedVideoPath\n ]);\n \n currentFile = this.processedVideoPath;\n }\n } catch (error) {\n console.error(\"Erreur lors de la fusion des pistes audio:\", error);\n }\n }\n\n // Si audioOnly est activé, convertir en MP3\n if (this.currentOptions?.audioOnly) {\n try {\n console.log(\"Conversion en MP3\");\n const audioPath = createTempFile({ extension: \"mp3\" });\n \n await execa(\"ffmpeg\", [\n \"-i\", currentFile,\n \"-vn\",\n \"-c:a\", \"libmp3lame\",\n \"-b:a\", \"192k\",\n \"-y\",\n audioPath\n ]);\n \n return audioPath;\n } catch (error) {\n console.error(\"Erreur lors de la conversion en MP3:\", error);\n return currentFile;\n }\n }\n\n return currentFile;\n }\n}\n\n/**\n * Creates and returns a new instance of ScreenCaptureKit.\n * @returns {ScreenCaptureKit} A new instance of the screen recorder.\n */\nexport default function () {\n return new ScreenCaptureKit();\n}\n\n/**\n * Retrieves the video codecs available on the system.\n * @returns {Map<string, string>} A map of available video codecs.\n * @private\n */\nfunction getCodecs() {\n const codecs = new Map([\n [\"h264\", \"H264\"],\n [\"hevc\", \"HEVC\"],\n [\"proRes422\", \"Apple ProRes 422\"],\n [\"proRes4444\", \"Apple ProRes 4444\"],\n ]);\n\n if (!supportsHevcHardwareEncoding) {\n codecs.delete(\"hevc\");\n }\n\n return codecs;\n}\n\n/**\n * Specific error for ScreenCaptureKit operations\n */\nexport class ScreenCaptureKitError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'ScreenCaptureKitError';\n }\n}\n\n/**\n * Retrieves the list of screens available for recording.\n * @returns {Promise<Screen[]>} A promise that resolves with an array of objects representing the screens.\n * @throws {ScreenCaptureKitError} If screen retrieval fails.\n */\nexport const screens = async (): Promise<Screen[]> => {\n const { stderr } = await execa(BIN, [\"list\", \"screens\"]);\n try {\n return JSON.parse(stderr);\n } catch {\n throw new ScreenCaptureKitError(`Failed to retrieve screens: ${stderr}`);\n }\n};\n\n/**\n * Retrieves the list of system audio devices available for recording.\n * @returns {Promise<AudioDevice[]>} A promise that resolves with an array of objects representing the audio devices.\n * @throws {ScreenCaptureKitError} If audio device retrieval fails.\n */\nexport const audioDevices = async (): Promise<AudioDevice[]> => {\n const { stderr } = await execa(BIN, [\"list\", \"audio-devices\"]);\n try {\n return JSON.parse(stderr);\n } catch {\n throw new ScreenCaptureKitError(`Failed to retrieve audio devices: ${stderr}`);\n }\n};\n\n/**\n * Retrieves the list of microphone devices available for recording.\n * @returns {Promise<MicrophoneDevice[]>} A promise that resolves with an array of objects representing the microphones.\n * @throws {ScreenCaptureKitError} If microphone retrieval fails.\n */\nexport const microphoneDevices = async (): Promise<MicrophoneDevice[]> => {\n const { stderr } = await execa(BIN, [\"list\", \"microphone-devices\"]);\n try {\n return JSON.parse(stderr);\n } catch {\n throw new ScreenCaptureKitError(`Failed to retrieve microphones: ${stderr}`);\n }\n};\n\n/**\n * Indicates whether the current system supports HDR capture.\n * @type {boolean}\n */\nexport const supportsHDRCapture = supportsHDR;\n\n/**\n * Map of video codecs available on the system.\n * @type {Map<string, string>}\n */\nexport const videoCodecs = getCodecs();\n\n// Fonction de remplacement pour temporaryFile sans créer le fichier\nfunction createTempFile(options: { extension?: string } = {}): string {\n const tempDir = os.tmpdir();\n const randomId = uuidv4();\n const extension = options.extension ? `.${options.extension}` : '';\n const tempFilePath = path.join(tempDir, `${randomId}${extension}`);\n \n // Ne pas créer le fichier, juste retourner le chemin\n return tempFilePath;\n}\n\n// Fonction personnalisée pour remplacer fileUrl\nfunction fileUrlFromPath(filePath: string): string {\n // Encodage des caractères spéciaux\n let pathName = filePath.replace(/\\\\/g, '/');\n \n // Assurez-vous que le chemin commence par un slash si ce n'est pas déjà le cas\n if (pathName[0] !== '/') {\n pathName = '/' + pathName;\n }\n \n // Encodage des caractères spéciaux dans l'URL\n pathName = encodeURI(pathName)\n // Encodage supplémentaire pour les caractères qui ne sont pas gérés par encodeURI\n .replace(/#/g, '%23')\n .replace(/\\?/g, '%3F');\n \n return `file://${pathName}`;\n}","import path from \"path\";\nimport { fileURLToPath } from \"url\";\n\ndeclare global {\n namespace NodeJS {\n interface Process {\n resourcesPath?: string;\n }\n }\n}\n\nexport const getPackageRoot = () => {\n if (process.env.NODE_ENV === \"test\") {\n return path.join(__dirname, \"..\", \"..\", \"dist\");\n }\n try {\n // Résolution via le point d'entrée du package\n const app = require(\"electron\").app;\n const packageMainPath = require.resolve(\"screencapturekit\");\n if (typeof process.resourcesPath === \"string\" && app?.isPackaged) {\n const finalPath = path.join(process.resourcesPath);\n return finalPath;\n }\n const finalPath = path.dirname(packageMainPath);\n return finalPath;\n } catch (e) {\n // Fallback pour le développement ES modules\n const __filename = fileURLToPath(import.meta.url);\n const finalPath = path.join(path.dirname(__filename));\n console.log(\"finalPath : ESM\", finalPath);\n return finalPath;\n }\n};\n\nexport const resolvePackagePath = (...segments: string[]) =>\n path.join(getPackageRoot(), ...segments);\n"],"mappings":"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","names":["path","finalPath","os","fs","uuidv4","path"]}