背景
go代码里面需要在环境变量path中有某些二进制文件才能运行成功,否则会有运行时错误。
先说下需求背景,业务需要有个地方需要解析音频视频图片的分辨率和长度等信息。经过不懈的搜索发现ffmpeg可以完成以上需求。
秉承着能用就行的原则在github上随便选择了一个go相关的ffmpeg的仓库,
github上的ffmpeg-go仓库,顺路贴在这里。
但是运行demo的时候发现还要安装ffmpeg,否则就会报没有找到相关文件的错误,windows就是解压ffmpeg相关二进制文件,直接放到path中,或者运行程序同级目录就好了。官方下载地址这里也贴一下:Download FFmpeg里面包含linux,windows,mac的下载。
新的问题又出现了,go本来可以是一个二进制文件就能运行,可否把这些二进制一起打包进编译好的文件中,这样其他人拿到二进制文件就能直接运行。(当然也可以一起把二进制文件给他)今天我们就尝试一起打包进一个二进制文件中。
解决
利用go 1.16新特性embed包在编译阶段将静态资源文件打包进编译好的程序中,并提供访问这些文件的能力。
加上go条件编译。就是在文件头加上 // go:build windows 这样。
就是给不同的环境编译的时候打包进去不同的文件。
然后运行的时候再解压出来,和运行文件同级目录,就会自动被识别。这样程序运行时就不会因为找不到文件报错了。运行结束的时候再吧这些解压出来的文件删除就好。
结构
│ ffmpeg_parsing.go # 具体实现
│ parsing.go # 接口定义
│
└─ffmpeg # 打包代码
│ init_darwin.go # mac打包代码
│ init_linux.go # linux打包代码
│ init_windows.go # windows打包代码
│ readme.md
│
└─bin # 二进制文件(从官网下载)
├─darwin # mac版本
│ ffmpeg
│ ffplay
│ ffprobe
│ ffserver
│
└─windows # windows版本
ffmpeg.exe
ffplay.exe
ffprobe.exe
代码片段
parsing.go
package mediaparsing
// IParsing 解析音频、视频、图片 获取分辨率时长等信息
type IParsing interface {
// Info 获取媒体信息
Info() (info MediaInfo, err error)
}
type MediaInfo struct {
Height int
Width int
Duration float64 // 秒
}
init_windows.go
//go:build windows
package ffmpeg
import (
"embed"
"io"
"os"
)
//go:embed bin/windows
var Bin embed.FS
// InitFfmpegExecuteFile 初始化ffmpeg需要的二进制文件
func InitFfmpegExecuteFile() (err error) {
err = copyFSFile("bin/windows/ffmpeg.exe", "ffmpeg.exe")
if err != nil {
return err
}
err = copyFSFile("bin/windows/ffplay.exe", "ffplay.exe")
if err != nil {
return err
}
err = copyFSFile("bin/windows/ffprobe.exe", "ffprobe.exe")
return err
}
// DeleteFfmpegExecuteFile 删除ffmpeg二进制文件
func DeleteFfmpegExecuteFile() {
_ = os.Remove("ffmpeg.exe")
_ = os.Remove("ffplay.exe")
_ = os.Remove("ffprobe.exe")
}
// copyFSFile 把fs文件复制到本地
func copyFSFile(FSFilePath, localPath string) error {
ffmpegOpen, err := Bin.Open(FSFilePath)
if err != nil {
return err
}
defer ffmpegOpen.Close()
ffmpegOut, err := os.Create(localPath)
if err != nil {
return err
}
defer ffmpegOut.Close()
_, err = io.Copy(ffmpegOut, ffmpegOpen)
return err
}
总结
这里只是给出一个比较取巧的解决方案,普通部署的时候基本用不到,如果用docker打包的话在基础镜像安装好ffmpeg就完美解决了。
不过倒是让我了解不是经常用到的embed和go的条件编译。