之前用 Node.js 开发了一款在线版 Mp4 转化器,有同学反映需求本地要安装 ffmpeg,运用起来比较麻烦。其实,咱们能够将该运用转化为 Elctron 桌面版,并将 ffmpeg 打包进去做成便携版,这样不必安装,还不必连网。

最终效果如下:

Electron + Vite + Vue3 + ts 打造 Mp4 转换器

electron-vite-vue 模板

本项目有两个版别:electron-vite-vue 脚手架版和 JS 原生版。原生版在烘托进程模块运用了很多的模板字符串,没有脚手架版那么便利。究竟 Vue 模板能够绑定数据。依据数据驱动是现代 JS 结构的精华。

模板是依据 Vite 官方的 template-vue-ts 脚手架建立的,所以绝大部分都是 Vite + Vue3 + ts 工程的文件(能够看作Vue 前端,其间 App.vue 是根组件,烘托进程模块的大部分逻辑都写在这儿),除了:

  • electron 文件夹:能够看作 Node 后端,其间 main.ts 是主进口,主进程模块的大部分逻辑都写在这儿。
  • electron-builder.json5:Electron 专属打包装备文件,JS 原生版的导报装备式放在 package.json 中。

有了 Vite 的加持,能够运用 Node.js ESM 包。

在生成的模板中集成 element-plus 有点问题:tsconfig.json 装备项 moduleResolution 设置成 Bundler 呼不出代码提示,我改成 Node 后就能够了。

如你所见,本质上,Electron 开发就是 JS 全栈开发。

桌面版的特性

桌面版和在线版的 Mp4 转化器比较,咱们共用了视频读取、视频转化代码逻辑。其他部分或多或少有些不同,究竟桌面版有不少原生操作,用户体会体会更好:

  • 菜单操作:桌面独有的功用,添加了 视频文件协助 两个操作进口
  • 多窗口操作:为便利显现,预览视频独自运用了一个窗口
  • 挑选视频文件运用 Electron 原生 dialog:不必上传视频后才能读取视频信息
  • 新增本地保存视频文件功用:相同运用 Elctron 原生 dialog
  • 运用 Element-plus UI 库:是不是比在线版美丽一些?
  • 运用进程间通讯(IPC):不必调用 Web API,离线也能转化

转化成功后会直接翻开预览视频,由于一旦将视频读入内存,再次转化很快就能完结,所以没有做预览视频的其他进口。

自界说菜单

本项目中,咱们运用 Menu.buildFromTemplate(menuTemplate) 来自界说原生运用菜单,template 是一个选项类型的数组,用于构建 MenuItem。经过模板创立的原生菜单是依据数据驱动的:

// electron/config.ts
import { MenuItem, MenuItemConstructorOptions, shell } from 'electron'
const isMac = process.platform === 'darwin'
const menuTemplate: (MenuItemConstructorOptions | MenuItem)[] = [
  {
    label: '视频文件',
    submenu: [
      // ...
      {
        id: 'saveFile',
        label: '保存视频',
        accelerator: 'CmdOrCtrl+S',
        enabled: false
      },
      { type: 'separator' },
      isMac
        ? {
            role: 'close',
            label: '退出',
            accelerator: 'Cmd+Q'
          }
        : {
            role: 'quit',
            label: '退出',
            accelerator: 'Ctrl+Q'
          }
    ]
  },
  {
    label: '协助',
    submenu: [
      // ...
      {
        id: 'support',
        label: '技术支持',
        click() {
          shell.openExternal('mailto:riafan@hotmail.com')
        }
      }
    ]
  }
]
export { menuTemplate }

在 macOS 大将 menu 设置成运用内菜单,在 Windows 和 Linux 上,menu 将会被设置成窗口顶部菜单。

关于 MenuItem 来说,除了设置 idlabel 特点外,在本项目中,咱们还设置了:

  • accelerator 快捷键:设置为 CmdOrCtrl+S,表明 macOS 上按 Cmd+S,Windows 上按 Ctrl+S 会保存视频
  • enabled 是否激活:本项目中,保存视频 菜单项默许是禁用的,只要翻开过视频才是激活的
  • click 点击菜单项的回调函数:本项目中,点击技术支持菜单项会翻开体系发送电子邮件的默许程序
  • role 界说菜单项的操作: 本项目中,设置 role: 'close' 会调用体系默许的退出运用操作,省去了对各体系分别设置 click 特点
  • type 界说菜单项类型:默许为 normal。设置为 separator 会在菜单项之间显现一条分割线

指定 click 特点,role 特点将被忽略。

翻开视频

进程间通讯 (IPC) 是在 Electron 中构建功用丰富的桌面运用程序的关键部分之一。 由于主进程和烘托器进程在 Electron 的进程模型具有不同的职责,因而 IPC 是履行许多常见使命的唯一办法。

下面是翻开视频的时序图:

Electron + Vite + Vue3 + ts 打造 Mp4 转换器

关于翻开视频来说,可能是经过挑选菜单项、点击挑选按钮或是拖放到按钮区触发的。点击挑选按钮或是拖放到按钮区是从烘托线程建议的,挑选视频后会回来视频的文件途径,因而应该运用烘托器进程到主进程双向通讯形式。而挑选菜单项是从主线程建议的,能够运用主进程到烘托器进程单向通讯形式。烘托器接收音讯后能够复用双向通讯形式,一样能够回来视频的文件途径。下面运用 contextBridge API 将这段代码露出给烘托器进程。

// preload.ts
selectFile: () => ipcRenderer.invoke('dialog:selectFile'),
onSelectFile: (callback: () => void) =>
  ipcRenderer.on('menu:selectFile', callback),

Electron 主进程侧(main.ts)需求运用 showOpenDialog 办法翻开对话框挑选一个视频文件:

// electron/main.ts
async function handleSelectFile() {
  const { canceled, filePaths } = await dialog.showOpenDialog({
    filters: [{ name: fileType, extensions: allowFormats }]
  })
  if (!canceled) {
    return filePaths[0]
  }
}

filters 用于规定用户可见或可选的特定类型范围,设置filtersextensions 特点会按文件后缀名过滤。假如 extensions 不设置成 ['*'],则对话框没有 所有文件 选项(Web 运用的文件挑选框总是有的),因而 Electron 中不必写文件类型查验的逻辑,指定文件类型即可。

showOpenDialog 办法会回来用户挑选的文件途径,假如对话框被取消了 ,则回来 undefined

读取、转化视频

这一块代码实现和 Web 版差不多,只是数据通讯运用的是 IPC 而不是 Web API。读取、转化视频的预加载脚本如下:

// preload.ts
readFile: (path: string) => ipcRenderer.invoke('video:readFile', path),
convertFile: (params: Params) =>
  ipcRenderer.invoke('video:convertFile', params)

如你所见,readFileconvertFile 都是运用烘托器进程到主进程双向通讯形式。

// electron/main.ts
.on('end', () => {
  console.log('file has been converted succesfully')
  createPreview({
    width,
    height
  })
  resolve(output)
})

视频转化成功后会另外创立一个窗口来预览视频。

预览视频

当然,创立预览窗口是在 Electron 主进程侧(main.ts)完结的。

// electron/main.ts
const win = new BrowserWindow({
  width: width + 16,
  height: height + 88,
  webPreferences: {
    preload: path.join(__dirname, 'preview.js')
  }
})
if (isDev) {
  win.loadURL(path.posix.join(VITE_DEV_SERVER_URL, 'preview.html'))
} else {
  win.loadFile(path.join(process.env.DIST, 'preview.html'))
}
// ...
win.removeMenu()

每个 Electron 运用都会为每个翻开的运用程序窗口 ( 与每个网页嵌入 ) 生成一个独自的烘托器进程,多窗口意味着多烘托器进程。通常新建运用程序窗口需求初始化窗口的宽高、预加载脚本和加载页面。

此处的预览窗口宽高是依据视频宽高计算出来的。为了安全,咱们还新建了预加载脚本 preview.js,在运用程序窗口结构办法中的 webPreferences 选项里将其附加到主进程。留意,咱们还需求为其额定装备一个进口,能够在 vite.config.ts 装备:

electron([
  // ...
  {
    entry: 'electron/preview.ts',
  }
])

预加载脚本 preview.ts 的代码如下:

// electron/preview.ts
import { contextBridge, ipcRenderer } from 'electron'
contextBridge.exposeInMainWorld('electronAPI', {
  previewFile: (callback: () => void) =>
    ipcRenderer.on('video:preview', callback),
  saveFile: (callback: () => void) =>
    ipcRenderer.invoke('dialog:saveFile', callback)
})

留意:此处的 saveFile 回调函数与 ‘electron/preload.ts’ 中的 saveFile 回调函数是一样的。

由于加载了新页面 preview.html,但 Vite 默许是单页面的,只要 index.html 这个单一进口,所以咱们还需求为其额定装备一个进口,能够在 vite.config.ts 装备:

build: {
  rollupOptions: {
    input: {
      index: path.join(__dirname, 'index.html'),
      preview: path.join(__dirname, 'preview.html'),
    }
  }
}

留意:开发环境下运行本脚手架,加载页面一定要运用服务器地址。

预览窗口不需求菜单,咱们运用 win.removeMenu() 将其移除。

保存视频

下面是保存视频的时序图:

Electron + Vite + Vue3 + ts 打造 Mp4 转换器

和翻开视频相似,咱们需求界说其预加载脚本:

// preload.ts
saveFile: () => ipcRenderer.invoke('dialog:saveFile'),
onSaveFile: (callback: () => void) =>
  ipcRenderer.on('menu:saveFile', callback)

Electron 主进程侧(main.ts)需求运用 showSaveDialog 办法翻开对话框来保存视频文件:

async function handleSaveFile() {
  const { canceled, filePath } = await dialog.showSaveDialog({
    filters: [{ name: fileType, extensions: ['mp4'] }],
    defaultPath: path.join(app.getPath('videos'), `${Date.now()}.mp4`)
  })
  if (!canceled) {
    return new Promise((resolve, reject) => {
      const rs = fs.createReadStream(output)
      const ws = fs.createWriteStream(filePath!)
      rs.pipe(ws)
      rs.on('end', () => {
        resolve('视频保存成功')
      }).on('error', (error: any) => {
        reject(error)
      })
    })
  }
}

保存视频实际上是个拷贝文件的进程,这儿运用了 fs 模块和 pipe 操作。

打包运用程序

之前提过,打包是在 electron-builder 中装备的:

{
  directories: {
    output: 'release'
  },
  files: [
    'dist-electron',
    'dist',
    "!**node_modules/ff*-static/bin/!(win32)",
    "!**node_modules/ff*-static/bin/win32/ia32"
  ],
  win: {
    icon: "res/icon.png",
    target: [
      {
        target: 'portable',
        arch: ['x64']
      }
    ],
    "artifactName": "${productName}_${version}.${ext}"
  }
}

咱们的方针是打包 win32 渠道 x64 架构下的 portable版别,装备很简单,重点说说 files

dist-electron 包含 Node.js 后端打包文件,dist 包含 Vue 前端打包文件。那两个文件正则表达式呢?

运用 Electron 打包的时分设置 asartrue,electron-builder 会智能的把一些 native 的程序(包含 exe履行文件)打包到 app.asar.unpacked中。也就是说,假如不运用文件正则表达式筛选特定渠道,会仿制 ffmpeg-staticffprobe-static 整个依赖包。那样打包文件就太大了。

留意:咱们需求经过替换来获取ffmpeg 二进制文件的途径,代码如下:

// Get the paths to the packaged versions of the binaries we want to use
const ffmpegPath = require('ffmpeg-static').replace(
  'app.asar',
  'app.asar.unpacked'
)
const ffprobePath = require('ffprobe-static').path.replace(
  'app.asar',
  'app.asar.unpacked'
)
// tell the ffmpeg package where it can find the needed binaries.
ffmpeg.setFfmpegPath(ffmpegPath)
ffmpeg.setFfprobePath(ffprobePath)

关于如安在 Electron 运用中包含 ffmpeg 二进制文件,可参考这篇文章。

打包文件是个漫长的进程,如何快速验证打包是否正确呢?

electron-builder --dir

运行这个 electron-builder 命令能够快速生成未紧缩的打包文件,能够协助排查问题。

本项目假如运用 JS 原生版打包,最终这个便携式 Mp4 转化器只要 70 M 左右。要知道,ffmpeg 那两个二进制文件都有60 M 左右。真是爽歪歪!

链接地址

  • electron-vite-vue 脚手架版 项目地址
  • Elctron + JS 原生版 项目地址
  • 原文:Electron Mp4 转化器
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。