目录

前言

正文

准备二进制文件

简单说说三个exe的功能

ffmpeg

ffplay

ffprobe

怎么使用

shell插件的简单使用

安装及注册

其中的方法

先使用sidecar方法

Command结构体

spawn方法

 获取视频文件的信息——ffprobe

使用command方法

通过cargo创建tauri项目

使用CommandChild


前言

这篇就来简单看看Sidecar和shell插件

sidecar,英文意思是  (摩托车的)跨斗,边车

SIDECAR中文(简体)翻译:剑桥词典SIDECAR翻译:(摩托车的)跨斗,边车。了解更多。https://dictionary.cambridge.org/zhs/%E8%AF%8D%E5%85%B8/%E8%8B%B1%E8%AF%AD-%E6%B1%89%E8%AF%AD-%E7%AE%80%E4%BD%93/sidecar从英文翻译中并不能得到什么有用的信息
Embedding External Binaries | Taurihttps://v2.tauri.app/develop/sidecar/

You may need to embed external binaries to add additional functionality to your application or prevent users from installing additional dependencies (e.g., Node.js or Python). We call this binary a sidecar.

——tauri官网

从官网中可以得到, sidecar其实代指二进制文件的意思。说白了。

在Windows操作系统,就是指exe文件的意思。

当然,简单使用一下

正文

准备二进制文件

要使用二进制文件,笔者搜了搜,首先看到了这个视频

Tauri嵌入外部二进制文件_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1q142197gU/?spm_id_from=333.337.search-card.all.click&vd_source=9a7a74ff1d725acfc83fd3567cd4a7c4本来想使用bun

安装 | Bun 文档 - Bun 中文https://bun.net.cn/docs/installation但感觉并不是很好用,就是一个执行JS和TS的二进制文件,没有其他功能。

笔者选择了其他二进制文件——FFmpeg

Download FFmpeghttps://ffmpeg.org/download.html安装完成之后,笔者是默认路径

C:\Program Files\ffmpeg\bin路径下,可以看到如下二进制文件

简单说说三个exe的功能

ffmpeg

主要用于音视频转码、处理、流媒体传输。

1、格式转换:将音视频文件从一种格式转码为另一种格式(如 MP4 转 AVI)。

2、编码/解码:支持几乎所有主流编解码器(如 H.264、AAC、VP9 等)。

3、编辑操作:剪切、合并视频或音频。调整分辨率等

4、提取/合成:从视频中提取音频(-vn 参数)。

ffplay

主要用于播放音视频文件

ffprobe

主要用于分析和显示音视频文件的详细信息

怎么使用

1、直接把ffmpeg的bin目录复制到src-tauri目录下

2、在配置文件中进行说明,bundle->externalBin,即

  "bundle": {
    "externalBin": [
      "bin/ffmpeg",
      "bin/ffplay",
      "bin/ffprobe"
    ],

3、按照操作系统修改exe文件的名字。笔者改的是ffmpeg-x86_64-pc-windows-msvc

不同操作系统,名字不一样。

从官网中,可以找到改名的脚本

Node.js 作为 Sidecar | Tauri - Tauri 框架https://v2.tauri.org.cn/learn/sidecar-nodejs/如果要使用脚本改名。

直接复制其中的内容并修改,笔者创建src/utils/rename.ts文件,

然后用bun运行

import { execSync } from 'child_process';
import fs from 'fs';

const ext = process.platform === 'win32' ? '.exe' : '';

const rustInfo = execSync('rustc -vV');
const targetTriple = /host: (\S+)/g.exec(rustInfo)[1];
if (!targetTriple) {
    console.error('Failed to determine platform target triple');
}
let oldExe=["ffmpeg","ffplay", "ffprobe"];
for (let old of oldExe) {
    fs.renameSync(
        `../../src-tauri/bin/${old}${ext}`,
        `../../src-tauri/bin/${old}-${targetTriple}${ext}`
    );
}

运行后,在dubug目录下,出现了这三个exe文件。

笔者想尝试传对象,没成功。

笔者也尝试过添加其他目录,比如bin/tool/ffmpeg,最后结果还是一样的

使用绝对路径,需要改名,没必要。

因此,直接复制到src-tauri目录下

实际上,也可以使用resources

比如

 "resources": {
      "bin/ffmpeg-x86_64-pc-windows-msvc.exe": "tool/ffmpeg.exe",
      "bin/ffplay-x86_64-pc-windows-msvc.exe": "tool/ffplay.exe",
      "bin/ffprobe-x86_64-pc-windows-msvc.exe": "tool/ffprobe.exe",
    }

编译后,可以在debug目录下发现如下内容

应该可以运行。

可以发现无论是resources还是externalBin都是在复制文件或文件夹

shell插件的简单使用

安装及注册

官网介绍如下

Shell | Taurihttps://v2.tauri.app/zh-cn/plugin/shell/Cargo.toml文件中进行安装。

tauri-plugin-shell = "2"

main函数中注册

        .plugin(tauri_plugin_shell::init())

默认权限

    "shell:default",

其中的方法

在Shell插件中有下面几个关键的方法

impl<R: Runtime> Shell<R> {
    pub fn command(&self, program: impl AsRef<OsStr>) -> Command 

    pub fn sidecar(&self, program: impl AsRef<Path>) -> Result<Command> 

    #[cfg(desktop)]
    #[deprecated(since = "2.1.0", note = "Use tauri-plugin-opener instead.")]
    pub fn open(&self, path: impl Into<String>, with: Option<open::Program>) -> Result<()> 

    #[cfg(mobile)]
    #[deprecated(since = "2.1.0", note = "Use tauri-plugin-opener instead.")]
    pub fn open(&self, path: impl Into<String>, _with: Option<open::Program>) -> Result<()>

 如果使用open方法,推荐使用opener插件替代

因此,可以认为,有两个关键的方法

command和sidecar

1、一个传递参数的类似是AsRef<OsStr>,表示 操作系统原生字符串,比如ls,mkdir等的

另一个是AsRef<Path>,表示 文件系统路径。比如 C:a/xxxx/xxxx/xx/

2、一个是系统路径中的任意可执行文件,比如说git、cargo、python

另一个是使用打包的二进制文件,需要路径,比如说,xxxx.exe。

先使用sidecar方法

项目结构如下

在use_ffplay.rs中,代码如下

use tauri::{AppHandle, command, Manager};
use tauri::path::BaseDirectory;
use tauri_plugin_shell::ShellExt;
#[command]
pub async  fn open_mp4(app:AppHandle){
    let shell=app.shell();
    shell.sidecar("ffplay")
        .unwrap()
        .arg("-i")
        .arg("C:/Users/26644/Videos/p/2024-01-21 16-35-04.mp4")
        .spawn()
        .expect("failed to execute process");
}

看看sidecar函数的签名 

pub fn sidecar(&self, program: impl AsRef<Path>) -> Result<Command> 

 传一个实现了trait  AsRef<Path>的类型,比如说&str,String

说白了,传一个二进制文件的路径

返回 Result<Command> 

使用中和opener、fs插件没什么区别,需要导入这个trait ShellExt 。然后使用其中的方法

结果成功,

也可以写绝对路径

    shell.sidecar("C:/Program Files/ffmpeg/bin/ffplay.exe")

 也没有问题

感觉用起来和opener没多大区别,当然,可以传递参数,更高级点

也可以使用resource的资源

    shell.sidecar("tool/ffplay.exe")

上面三个路径,笔者尝试,都没有问题。

Command结构体

#[derive(Debug)]
pub struct Command {
    cmd: StdCommand,
    raw_out: bool,
}

 这是一个结构体,实现了Debug,还可以打印。

下面是实现的方法

pub(crate) fn new<S: AsRef<OsStr>>(program: S) -> Self
pub(crate) fn new_sidecar<S: AsRef<Path>>(program: S) -> crate::Result<Self>
pub fn arg<S: AsRef<OsStr>>(mut self, arg: S) -> Self
pub fn args<I, S>(mut self, args: I) -> Self where I: IntoIterator<Item = S>, S: AsRef<OsStr>
pub fn env_clear(mut self) -> Self
pub fn env<K, V>(mut self, key: K, value: V) -> Self where K: AsRef<OsStr>, V: AsRef<OsStr>
pub fn envs<I, K, V>(mut self, envs: I) -> Self where I: IntoIterator<Item = (K, V)>, K: AsRef<OsStr>, V: AsRef<OsStr>
pub fn current_dir<P: AsRef<Path>>(mut self, current_dir: P) -> Self
pub fn set_raw_out(mut self, raw_out: bool) -> Self
pub fn spawn(self) -> crate::Result<(Receiver<CommandEvent>, CommandChild)>
pub async fn status(self) -> crate::Result<ExitStatus>
pub async fn output(self) -> crate::Result<Output>

1、new方法:显而易见是初始化,

2、arg和args:是用来传递参数的,args是一个可迭代的参数列表,其中每个参数可以是&str,获取&String等的

3、env和envs:是用用来设置环境变量的

4、current_dir:设置命令的工作目录。

5、set_raw_out:配置命令的输出是否为原始字节,用于控制输出

6、spawn:启动命令并返回一个通道用于接收事件,返回值

Receiver<CommandEvent>:接收子进程事件(Stdout/Stderr/Terminated/Error)。

CommandChild:用于向子进程写入数据(通过 stdin_writer)。

7、output:运行命令并等待其完成,返回命令的输出和退出状态

spawn方法

spawn 方法比较重要的,是用于启动子进程的方法。它会异步运行命令,并返回一个通道接收器(Receiver<CommandEvent>)和一个子进程句柄CommandChild

 pub fn spawn(self) -> crate::Result<(Receiver<CommandEvent>, CommandChild)>

其中CommandEvent如下

#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum CommandEvent {

    Stderr(Vec<u8>),


    Stdout(Vec<u8>),

    Error(String),

    Terminated(TerminatedPayload),
}

是个enum,使用如下

use tauri_plugin_shell::process::CommandEvent; 
while let Some(event) = rx.recv().await {
        match event {
            CommandEvent::Stdout(line) => {
                println!("stdout: {}", String::from_utf8(line).unwrap());
            }
            CommandEvent::Stderr(line) => {
                println!("stderr: {}", String::from_utf8(line).unwrap());
            }
            CommandEvent::Terminated(payload) => {
                println!("Process terminated with code: {:?}", payload.code);
            }
            CommandEvent::Error(err) => {
                eprintln!("Error: {}", err);
            }
            _ => {}
        }
    }

 打印了许多东西

为什么打印的stderr,笔者也不能确定,可能是没有配置权限。

总之,运行了

 获取视频文件的信息——ffprobe

[总结]FFMPEG命令行工具之ffprobe详解-CSDN博客https://blog.csdn.net/ice_ly000/article/details/87870446直接给出代码

#[command]
pub async fn get_mp4_info(state: State<'_, Shell<Wry>>) -> Result<String, String> {
    let shell = state.inner();
    let (mut rx, mut child) = shell.sidecar("tool/ffprobe.exe")
        .map_err(|e| e.to_string())?
        .args(["-v", "quiet", "-print_format", "json", "-show_format", "-show_streams"])
        .arg("C:/Users/26644/Videos/p/2024-01-21 16-35-04.mp4")
        .spawn()
        .map_err(|e| e.to_string())?;
    let mut stdout = Vec::new();
    while let Some(event) = rx.recv().await {
        if let CommandEvent::Stdout(line) = event {
            println!("stdout: {}", String::from_utf8(line.clone()).unwrap());
            stdout.extend(line);
        }
    }
    let output = String::from_utf8(stdout).map_err(|e| e.to_string())?;
    Ok(output)
}

笔者使用了State,直接使用 

前端打印的结果如下

当然输出了很多。

具体含义可参考有关ffmpeg的教程

使用command方法

#[command]
pub async fn use_command_method(app_handle: AppHandle){
    let shell = app_handle.shell();
    let out=shell.command("ffmpeg")
        .arg("--version")
        .output().await;
    match out {
        Ok(output) => {
            println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
            println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
        }
        Err(e) => {
            eprintln!("Error: {}", e);
        }
    }
}

笔者配置了ffmpeg的系统环境,可以获取其信息

结果如下

实际上这ffmpeg是调用的本机的。

比如说,笔者电脑配置了GDAL(开源的地理库)的环境

    let out=shell.command("gdalinfo")
        .arg("--version")
        .output().await;

 结果如下

可以看出这个command方法,实际上就是命令行cmd,但是这个是模拟的命令行

通过cargo创建tauri项目

笔者想使用pnpm,但是报错了,笔者没解决,因此使用cargo

代码如下

#[command]
pub async fn use_command_method(app_handle: AppHandle){
    let shell = app_handle.shell();
    shell.command("cargo")
        .arg("create-tauri-app")
        .arg("tt")
        .args(["-m", "pnpm"])
        .args(["-t", "react-ts"])
        .arg("-y")
        .spawn()
        .expect("创建失败");
}

 运行后,在src-tauri目录下出现了tt目录。创建成功。

使用CommandChild

实现的方法如下

impl CommandChild {

    pub fn write(&mut self, buf: &[u8]) -> crate::Result<()> {
  
    }
    pub fn kill(self) -> crate::Result<()> {

    }

    pub fn pid(&self) -> u32 {

    }
}

因此,代码如下

#[command]
pub async fn use_command_method(app_handle: AppHandle) {
    let shell = app_handle.shell();

    let (mut rx, mut child) = shell.command("python")
        .arg("-i")
        .spawn()
        .expect("创建失败");
    child.write(b"def main():return 'hello world' \n");
    child.write(b"\n");
    child.write(b"main()\n");

    while let Some(event) = rx.recv().await {
       ....
    }
}

使用python 的交互模式

定义了一个main函数,返回字符串hello world

结果如下

 运行成功

但是笔者想这样操作

    let (mut rx, mut child) = shell.command("cargo")
        .arg("create-tauri-app")
        .spawn()
        .expect("创建失败");
    child.write(b"tts\n");
    child.write(b"\n");

 想前面一样创建cargo项目,发现不行,报错了

stderr: error: IO error: not a terminal: not a terminal

Process terminated with code: Some(1)

 必须是个终端,比如Python和node就可以

比如Java的终端jshell

    let (mut rx, mut child) = shell.command("jshell")
        .spawn()
        .expect("创建失败");
    child.write(b"System.out.println("hello world")\n");

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐