🖥️Electron实现录屏软件(二)——指定区域录制

简介: 🖥️Electron实现录屏软件(二)——指定区域录制

前言

在上一期我们使用Electron实现了一个自己的录屏软件,评论区有小伙伴说如何支持区域录制。诚然区域录制也是一个十分常见的需求,所以今天我们就来实现一下录屏软件的区域录制。 22.png

前置优化

在开始区域录制之前,先优化一下之前的一些实现方式,以及重新定义一些概念。方便我们更好地开始实现后面的逻辑。

23.png


重新定义清晰度

在上一期中我们定义了几种清晰度标准:

  • 超清:3840x2160分辨率
  • 高清:1280x720分辨率
  • 标清:720x480分辨率

然后通过ffmpeg指定分辨率去录制不同质量的视频,但其实这样的实现是有一些问题的。试想一下你的录制硬件设备最高的分辨率都没达到超清的分辨率,那么设置超清的分辨率也是没有意义的。

所以录制的时候我会不指定分辨率,这样ffmpeg就会采用最高分辨率来录制,录制完成之后再把视频转成指定的分辨率。这里的分辨率我也重新定义了一下,现在是以百分比来设置。

const DEFINITION_LIST = [
  { label: '100%', value: '1' },
  { label: '75%', value: '0.75' },
  { label: '50%', value: '0.5' },
  { label: '25%', value: '0.25' }
]

24.png


录制命令就改成了下面的样子

const ffmpegCommand = `${ffmpegPath} -f avfoundation -r ${frameRate} -i "1" -c:v libx264 -preset ultrafast ${fileName}`

视频格式转换

录制完成之后,再调用ffmpeg的视频格式转换功能,这个时候顺便把分辨率一起设置一下。录制完成后对应的子进程就会退出,此时监听子进程的退出事件,调用转码方法

  ffmpegProcess.on('exit', (code, signal) => {
    console.log(`Recording process exited with code ${code} and signal ${signal}`)
    afterRecord()
  })

下面是afterRecord的部分实现,解释一下实现流程:

  • fileName是录制的默认mp4文件,output是我们要输出的视频文件
  • 根据选择导出的视频格式,将mp4文件转成对应的格式
  • 启动一个子进程,执行ffmpeg命令处理视频
  • 视频处理完成后删除默认mp4文件,并打开保存视频的文件夹
    const output = `${FILE_PATH}/record-${moment().format('YYYYMMDDHHmmss')}.${ext}`
    if (ext === 'mp4') {
      command = `${ffmpegPath} -i ${fileName} -vf "scale=${scale}" -c:a copy ${output}`
    } else if (ext === 'webm') {
      command = `${ffmpegPath} -i ${fileName} -vf "scale=${scale}" -c:v libvpx -c:a libvorbis ${output}`
    } else if (ext === 'gif') {
      command = `${ffmpegPath} -i ${fileName} -vf "fps=15,scale=${scale}:flags=lanczos" -c:v gif ${output}`
    }
    let progress = spawn(command, { shell: true })
    progress.stderr.on('data', (data) => {
      console.log(`FFmpeg Convert Log: ${data}`)
    })
    progress.on('exit', (code, signal) => {
      console.log(`Recording process exited with code ${code} and signal ${signal}`)
      fs.unlinkSync(fileName)
      if (code == 0) {
        shell.openPath(FILE_PATH)
        progress = null
        ffmpegProcess = null
      }
    })


区域窗口

下面来实现区域的录制,首先我们要实现一个能框住某个区域的窗口。这样我们在录制的时候,视频的录制范围就是区域的范围。

25.png

我在托盘菜单这里加了一个选取区域的菜单,点击这个菜单的时候会打开一个新窗口,这个新窗口是可以移动并且调整大小的。后续的屏幕录制就会以这个窗口的大小位置为基准录制。 26.png 链接

实现逻辑并不复杂,具体如下:

  • 创建一个新窗口,framefalse表示无边框,transparenttrue表示透明窗口
  • loadURL加载一个空的html页面
  • dragWindow方法实现整体窗口可拖动,具体在第一期实现过,这里就不再赘述
  • executeJavaScript给新窗口注入一些样式


let areaWindow
export const closeArea = () => {
  if (areaWindow) {
    areaWindow.close()
    areaWindow = null
  }
}

export const openRecordArea = () => {
  if (areaWindow) {
    closeArea()
    return
  }
  const newWindow = new BrowserWindow({
    width: 600,
    height: 600,
    frame: false,
    transparent: true,
    webPreferences: {
      preload: PRELOAD_URL,
      sandbox: false
    }
  })
  areaWindow = newWindow
  newWindow.loadURL(
    `data:text/html;charset=utf-8,${encodeURIComponent('<html><body></body></html>')}`
  )
  newWindow.on('ready-to-show', () => {
    newWindow.show()
  })
  newWindow.on('close', () => {
    areaWindow = null
  })
  newWindow.webContents.on('did-finish-load', () => {
    dragWindow(newWindow)
    newWindow.webContents.executeJavaScript(`
    const customStyles = \`
      html, body {
        padding: 0;
        margin: 0;
        background: transparent;
        border-radius:4px;
      }
      body {
        border: 2px dashed #ccc;
      }
      #root {
        display: none
      }
    \`;

    const styleTag = document.createElement('style');
    styleTag.textContent = customStyles;
    document.head.appendChild(styleTag);
  `)
  })
}

指定区域录制

开始录制之前要先介绍三个概念,物理像素、逻辑像素跟DPI。

物理像素是显示屏上的实际光点或发光元素,是硬件层面上的概念,它们直接映射到显示设备的硬件组件。

逻辑像素是在软件层面上的概念,是应用程序和操作系统中用于描述图像和界面的基本单位。逻辑像素通常不直接映射到硬件上的物理像素,而是由操作系统和图形引擎处理,以适应不同的显示设备和分辨率。

DPI代表“每英寸点数”(Dots Per Inch),它是一种用来度量打印设备、扫描仪、显示器或数字图像设备分辨率的单位。DPI表示在一英寸的空间内有多少个点或像素。

对于我们的功能需要注意的就是,ffmpeg操作的像素都是物理像素,而我们的录制区域获取到的值都是逻辑像素。比如说我们通过以下的代码获取录制区域的位置大小信息:


const size = areaWindow.getSize()
const position = areaWindow.getPosition()
const [width, height] = size
const [left, top] = position

这里的高度、宽度、距离等等都是逻辑像素。

我们是使用ffmpeg的裁剪来录制指定区域,比如说下面的命令

ffmpeg -f avfoundation -r 30 -i "1" -vf "crop=800:600:100:100" output.mp4

-vf "crop=800:600:100:100" 的意思是裁剪视频,使其宽度为800,高度为600,并且从左上角坐标 (100, 100) 开始裁剪。

所以我们在计算窗口大小,即视频的分辨率大小时,是需要将逻辑像素转换成物理像素的。物理像素=逻辑像素×DPI。所以我们获取录制区域信息的时候,需要这样计算

let cropRect = {}
const size = areaWindow.getSize()
const position = areaWindow.getPosition()
const [width, height] = size
const [left, top] = position
const mainScreen = screen.getPrimaryDisplay()
const { scaleFactor } = mainScreen
cropRect = {
  width: width * scaleFactor,
  height: height * scaleFactor,
  left: left * scaleFactor,
  top: top * scaleFactor
}


接着就可以使用crop进行区域录制了

  const { frameRate } = config
  fileName = `${FILE_PATH}/${moment().format('YYYYMMDDHHmmss')}.mp4`
  const cropString = !isEmpty(cropRect)
    ? `-vf "crop=${cropRect.width}:${cropRect.height}:${cropRect.left}:${cropRect.top}"`
    : ''
  /**统一先录制为mp4,避免受硬件影响 */
  const ffmpegCommand = `${ffmpegPath} -f avfoundation -r ${frameRate} -i "1" ${cropString} -c:v libx264 -preset ultrafast ${fileName}`

这里再看一个问题,如果我的选区是600*600DPI2,那么录制出来的视频分辨率是多少呢?

44.png


没错,就是1200*1200,那么再回到我们上面的清晰度配置项,如果我的清晰度选择了50%,那么其实最终的产物文件分辨率应该是600*600。所以在录制默认文件完成之后,还需要根据清晰度再去做一遍处理。

  • rate就是选择的清晰度
  • physicalWidthphysicalHeight分别对应屏幕的物理宽度、物理高度
  • 原始的分辨率应该就是physicalWidth * scaleFactor,这个时候乘以选择的清晰度就是设置的分辨率(physicalWidth * scaleFactor * rate)
  • 算一下裁剪的区域占原始区域多少,再乘一下这个比例,就是最终指定区域的视频分辨率。(注意cropRect.width我们已经乘过DPI了)
let command
let rate = Number(definition)
const mainScreen = screen.getPrimaryDisplay()
const { scaleFactor } = mainScreen
const { width: physicalWidth, height: physicalHeight } = mainScreen.size
let scale = [physicalWidth * scaleFactor * rate, physicalHeight * scaleFactor * rate]
if (!isEmpty(cropRect)) {
  const cropXRate = cropRect.width / (physicalWidth * scaleFactor)
  const cropYRate = cropRect.height / (physicalHeight * scaleFactor)
  scale = [scale[0] * cropXRate, scale[1] * cropYRate]
}

scale = `${Math.round(scale[0])}:${Math.round(scale[1])}`
command = `${ffmpegPath} -i ${fileName} -vf "scale=${scale}" -c:a copy ${output}`

45.png 链接

最后

以上就是本文介绍的指定区域录屏功能,如果你有一些不同的想法,欢迎评论区交流。如果你觉得有所收获的话,点点关注点点赞吧~



相关文章
|
5月前
|
前端开发
PC端01,桌面端,electron的开发,electron的开发的系列课程,软件开发必备流程,electron的讲解,electron的开发,vitepress博主的gitee链接,PC端效率软件
PC端01,桌面端,electron的开发,electron的开发的系列课程,软件开发必备流程,electron的讲解,electron的开发,vitepress博主的gitee链接,PC端效率软件
PC端01,桌面端,electron的开发,electron的开发的系列课程,软件开发必备流程,electron的讲解,electron的开发,vitepress博主的gitee链接,PC端效率软件
|
7月前
|
前端开发 API CDN
Electron实现你自己的Markdown编辑软件
Electron实现你自己的Markdown编辑软件
|
7月前
|
存储 编解码 JavaScript
🔥Electron打造你自己的录屏软件🔥
🔥Electron打造你自己的录屏软件🔥
|
7月前
|
前端开发 数据可视化 iOS开发
基于electron快速将任意网站打包成跨平台的桌面端软件
基于electron快速将任意网站打包成跨平台的桌面端软件
245 1
electron+vue音乐集成软件
上下曲播放有很多方法,可已根据当前播放的URL和列表数组里去遍历和这一个URL相等的那个数组的索引,当点击下一曲时,把那个索引加一,然后获取到这个数组的当前加一的索引,上一曲也是一样,不过这样有个问题,就是点击下一曲时,如有15个歌曲的数组 我判断当前的索引加一小于15
|
1月前
|
JSON JavaScript 前端开发
开发桌面程序-Electron入门
【10月更文挑战第16天】Electron 是一个使用 JavaScript、HTML 和 CSS 构建跨平台桌面应用的框架,嵌入了 Chromium 和 Node.js。本文介绍了如何搭建 Electron 开发环境,包括安装 Node.js、创建项目、配置 main.js 和打包应用。通过简单的步骤,你可以快速创建并运行一个基本的 Electron 应用程序。
开发桌面程序-Electron入门
|
4月前
|
JavaScript 开发工具
Electron 开发过程中主进程的无法看到 console.log 输出怎么办
Electron 开发过程中主进程的无法看到 console.log 输出怎么办
|
7月前
|
移动开发 开发框架 JavaScript
Vue3 Vite electron 开发桌面程序
Vue3 Vite electron 开发桌面程序
351 0
|
前端开发 算法 JavaScript
从零开始开发图床工具:使用 Gitee 和 Electron 实现上传、管理和分享(下)
从零开始开发图床工具:使用 Gitee 和 Electron 实现上传、管理和分享(下)
198 0
|
存储 Web App开发 JavaScript
从零开始开发图床工具:使用 Gitee 和 Electron 实现上传、管理和分享(上)
从零开始开发图床工具:使用 Gitee 和 Electron 实现上传、管理和分享(上)
248 0