侧边栏壁纸
博主头像
术业有道之编程博主等级

亦是三月纷飞雨,亦是人间惊鸿客。亦是秋霜去叶多,亦是风华正当时。

  • 累计撰写 99 篇文章
  • 累计创建 50 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

nodejs截屏

Administrator
2022-08-01 / 0 评论 / 0 点赞 / 510 阅读 / 9690 字

写在前面

前段时间研究如何使用nodejs静默截屏,搜索了很多库,发现了screenshot-desktop这个项目。
我选它的原因只有一个,在内存中生成图片Promise<Buffer>对象,不在磁盘上生成临时文件。

一、安装

npm install screenshot-desktop
TypeScript需要额外安装
npm install @types/screenshot-desktop

二、基于screenshot-desktop定义截屏业务(可以直接拿来用)

import screenshotDesktop from "screenshot-desktop";
import * as fs from "fs";

class ScreenService {
    /**
     * 截屏事件开关
     */
    private suspendFag: boolean = false;
    private indexSuspend: number = 0;

    /**
     * 截取屏幕
     */
    createScreenshot(): Promise<Buffer> {
        return screenshotDesktop({format: "png"})
            .then(img => {
                return img;
            }).catch(err => {
                console.log('截屏失败', err);
                return err;
            })
    }

    /**
     * 定时器触发截取屏幕
     * @param callback 回调函数处理截取的图片buffer
     */
    startScreenshotTimer(callback: Function) {
        let time = new Date();
        if (this.isSuspend()) {
            if (this.indexSuspend > 0) return
            console.log("截屏暂停[", this.indexSuspend, "]", time)
            this.indexSuspend++
        } else {
            console.log("我开始截屏了", time)
            this.createScreenshot().then((img): void => {
                callback(img);
            })
        }
    }

    isSuspend(): boolean {
        return this.suspendFag
    }

    /**
     * 暂停截取
     */
    suspend() {
        if (this.isSuspend()) return
        this.suspendFag = true;
        console.log("截屏任务暂停")
    }

    /**
     * 恢复截取
     */
    continued() {
        if (!this.isSuspend()) return
        this.suspendFag = false;
        console.log("截屏任务继续开始")
    }

}
export const screenService = new ScreenService()

三、使用

/**
 * 截取屏幕的定时,单位ms
 */
const SCREENSHOT_INTERVAL = 1000;
setInterval((): void => {
            //todo test
            if (screenService.isSuspend()) return;
            if (this.socketId == undefined) {
                console.warn("没有 socketId")
                screenService.suspend()
                return
            }
            if (this.rooms.length <= 0) {
                console.warn("没有要接收的rooms")
                screenService.suspend()
                return
            }
            if (this.desktops.length >= this.desktopsMax) {
                console.warn("desktops超过上限,放弃本次增加:", new Date())
                screenService.suspend()
                return
            }
            //这里最重要的调用截屏方法
            screenService.startScreenshotTimer(((imgBuffer: Buffer): void => {
                // imgBuffer 对象就是截取到的屏幕数据,这里可以开始压缩图片,写磁盘等操作
              
            }))
        }, SCREENSHOT_INTERVAL)

四、插曲

发现打成exe程序后,无法在windows下工作。在开源项目下找到了一个run correctly with bundlers问题跟我遇到的一样,但是已经提交了修复方案,作者没有合并。
于是我按照说明进行本地源代码修改,成功搞定。作者回复我说已经进行合并了。但截止本文发布时间,screenshot-desktop版本为1.12.7@types/screenshot-desktop版本为1.12.0,并没有新的版本发布。也就意味着需要使用本地修改源代码的方案来使用。

五、更改源代码以支持windows打包运行

  • 新建一个文件名index.js(必须叫这个名字)下面是完整的文件内容
const Promise = require('pinkie-promise')
const exec = require('child_process').exec
const temp = require('temp')
const path = require('path')
const utils = require('../utils')
const fs = require('fs')
const os = require('os')
const {
  readAndUnlinkP,
  defaultAll
} = utils

function copyToTemp() {
  const tmpBat = path.join(os.tmpdir(), 'screenCapture', 'screenCapture_1.3.2.bat')
  const tmpManifest = path.join(os.tmpdir(), 'screenCapture', 'app.manifest')
  const includeBat = path.join(__dirname, 'screenCapture_1.3.2.bat').replace('app.asar', 'app.asar.unpacked')
  const includeManifest = path.join(__dirname, 'app.manifest').replace('app.asar', 'app.asar.unpacked')
  if (!fs.existsSync(tmpBat)) {
    fs.mkdirSync(path.join(os.tmpdir(), 'screenCapture'))
    const sourceData = {
      bat: fs.readFileSync(includeBat),
      manifest: fs.readFileSync(includeManifest)
    }
    fs.writeFileSync(tmpBat, sourceData.bat)
    fs.writeFileSync(tmpManifest, sourceData.manifest)
  }
  return tmpBat
}

function windowsSnapshot (options = {}) {
  return new Promise((resolve, reject) => {
    const displayName = options.screen
    const format = options.format || 'jpg'
    const tmpPath = temp.path({
      suffix: `.${format}`
    })
    const imgPath = path.resolve(options.filename || tmpPath)

    const displayChoice = displayName ? ` /d "${displayName}"` : ''

    const tmpBat = copyToTemp()

    exec('"' + tmpBat + '" "' + imgPath + '" ' + displayChoice, {
      cwd: path.join(os.tmpdir(), 'screenCapture'),
      windowsHide: true
    }, (err, stdout) => {
      if (err) {
        return reject(err)
      } else {
        if (options.filename) {
          resolve(imgPath)
        } else {
          readAndUnlinkP(tmpPath)
            .then(resolve)
            .catch(reject)
        }
      }
    })
  })
}

const EXAMPLE_DISPLAYS_OUTPUT = '\r\nC:\\Users\\devetry\\screenshot-desktop\\lib\\win32>//  2>nul  || \r\n\\.\\DISPLAY1;0;1920;1080;0\r\n\\.\\DISPLAY2;0;3840;1080;1920\r\n'

function parseDisplaysOutput (output) {
  const displaysStartPattern = /2>nul {2}\|\| /
  const {
    0: match,
    index
  } = displaysStartPattern.exec(output)
  return output.slice(index + match.length)
    .split('\n')
    .map(s => s.replace(/[\n\r]/g, ''))
    .map(s => s.match(/(.*?);(.?\d+);(.?\d+);(.?\d+);(.?\d+);(.?\d*[\.,]?\d+)/)) // eslint-disable-line
    .filter(s => s)
    .map(m => ({
      id: m[1],
      name: m[1],
      top: +m[2],
      right: +m[3],
      bottom: +m[4],
      left: +m[5],
      dpiScale: +m[6].replace(',', '.')
    }))
    .map(d => Object.assign(d, {
      height: d.bottom - d.top,
      width: d.right - d.left
    }))
}

function listDisplays () {
  return new Promise((resolve, reject) => {
    const tmpBat = copyToTemp()
    exec(
      '"' + tmpBat + '" /list', {
        cwd: path.join(os.tmpdir(), 'screenCapture')
      },
      (err, stdout) => {
        if (err) {
          return reject(err)
        }
        resolve(parseDisplaysOutput(stdout))
      })
  })
}

windowsSnapshot.listDisplays = listDisplays
windowsSnapshot.availableDisplays = listDisplays
windowsSnapshot.parseDisplaysOutput = parseDisplaysOutput
windowsSnapshot.EXAMPLE_DISPLAYS_OUTPUT = EXAMPLE_DISPLAYS_OUTPUT
windowsSnapshot.all = () => defaultAll(windowsSnapshot)

module.exports = windowsSnapshot

  • 把刚刚弄好的index.js复制(强制覆盖)到你的项目node_modules/screenshot-desktop文件夹下

然后就可以正常打包运行了。

六、送几个nodejs打包命令

  • package.json文件
{
  "name": "rdvc-client-core",
  "version": "0.0.0",
  "private": true,
  "bin": "./build/client/core/src/index.js",
  "scripts": {
    "dev": "set DEBUG=express:* & nodemon src/index.ts",
    "build": "tsc",
    "prod": "node ./build/client/core/src/index.js",
    "pkg": "pkg . --output=bin/rdvc-client-core"
  },
  "pkg": {
    "assets": [
      "views/**/*"
    ],
    "scripts": "./build/client/core/src/index.js",
    "targets": [
      "node16-macos-x64",
      "node16-win-x64",
      "node12-linux"
    ],
    "outputPath": "dist"
  },
 "dependencies": {
    "express-handlebars": "^6.0.6",
    "@squoosh/lib": "^0.4.0",
    "express": "^4.16.1",
    "screenshot-desktop": "^1.12.7",
    "socket.io-client": "^4.5.1",
    "socket.io": "^4.5.1"
  },
  "devDependencies": {
    "@types/express": "^4.16.1",
    "@types/node": "^14.14.22",
    "@types/screenshot-desktop": "^1.12.0",
    "nodemon": "^2.0.16",
    "ts-node": "^10.8.1",
    "typescript": "^4.7.3"
  }
}

image.png

  • ./build/client/core/src/index.js 这个文件是编译后生成的js入口文件,根据自己的项目路径来(build目录与src目录平级)

  • 运行npm run dev 进行开发模式启动(更改实时生效,需要安装nodemon)

  • 运行npm run build 进行编译

  • 运行npm run prod 使用编译后的文件启动

  • 运行npm run pkg 打包成mac、win、liunx可执行文件

    个人公众号

0

评论区