Flutter开发Mac桌面应用实现自动提取生成视频字幕文件

 更新时间:2023年03月29日 14:41:57   作者:loongwind  
这篇文章主要为大家介绍了Flutter开发Mac桌面应用实现自动提取生成视频字幕文件示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

前言

前段时间准备做一个视频,最后需要添加字幕,手动添加太麻烦了就想在网上找一个能自动提取字幕的软件或服务,确实是找到了,但是免费版基本上都有诸多限制,比如现在视频时长等等,后来在 Github 找到一个开源的版本是使用云平台的语音识别实现的,云服务的语音识别是有免费的额度的,对于个人使用来说一般是够用了,项目地址:video-srt-windows ,大致实现流程如下:

  • 使用 ffmpeg 提取视频的音频文件
  • 将音频文件上传到云平台的对象存储
  • 调用云平台的语音识别 api 进行文字识别
  • 生成字幕文件

下载 release 版本测试了一下效果还可以,只需要修改个别识别有误的词就行,功能完全满足我的需求;但是遗憾的是该项目只提供了 Windows 版本,而没有 Mac 版本的 ,虽然作者也提供了一个 CLI 命令行版本可以在 Mac 上使用,但是对于普通用户来说使用起来还是不是很方便,于是产生了开发一个 Mac 版。

思路

该开源项目作者是用 Go 语言写的,我本人擅长的是 Flutter 开发,所以首先想到的就是通过 Flutter 开发一个 Mac 版的桌面应用,将 CLI 项目通过 Go 编译成 Mac 的可执行文件内置到 Flutter 项目中,再通过 Dart 调用 shell 命令进行执行从而实现软件的功能。

效果

实现

下面就来看看整个项目是如何一步步最终实现上面的效果的。

编译 Mac 版可执行文件

首先将 CLI 项目 clone 到本地,然后使用 go build命令编译对应平台的可执行文件,如下:

# Mac M1/M2 Arm 架构 CPU
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -o video-srt-arm64 main.go
# Mac Amd 架构 CPU
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o video-srt-amd64 main.go

执行以上文件分别生成 armamd 架构的可执行文件 video-srt-arm64 和 video-srt-amd64。

内置可执行文件和 ffmpeg

将上一步生成的对应平台的可执行文件修改为 video-srt 和配置文件 config.ini以及 ffmpeg文件放到一个文件夹中打包成 video-srt.zip压缩包减少包体积。

因为项目需要使用到 ffmpeg ,所以需要把 ffmpeg 也内置到项目中

通过 Xcode 将 video-srt.zip文件添加到项目的 Resources 文件夹下

然后就是通过代码在程序启动时将内置的压缩包解压到指定位置,这里解压使用了 archive库,核心代码如下:

// 目录名称
const String VIDEO_SRT = "video-srt";
class ZipRepository{
  static Future<void> unzip(String zipFile, String targetDir) async{
    final inputStream = InputFileStream(zipFile);
    final archive = ZipDecoder().decodeBuffer(inputStream);
    extractArchiveToDisk(archive, targetDir);
    return;
  }
  static Future<void> unzipVideoSrt() async{
    var workDirPath = await PathUtils.getWorkDirPath();
    // 创建工作目录下的 video-srt 目录
    var videoSrtFile = Directory("$workDirPath/$VIDEO_SRT");
    // 如果已经存在则不重复解压
    if(await videoSrtFile.exists()){
      return;
    }
    // 解压
    await unzip(VIDEO_SRT_ZIP_PATH, "$workDirPath");
    return;
  }
}

这里还用到了 path_provider库用于获取相关目录:

// 工作目录名称
const String WORK_DIR_NAME = "videoSrt";
class PathUtils{
  static String? workDirPath;
  static Future<String> getWorkDirPath() async{
    if(workDirPath != null){
      return workDirPath!;
    }
    // 获取 library 目录
    Directory tempDir = await getLibraryDirectory();
    var workDir = "${tempDir.path}/$WORK_DIR_NAME";
    var dir = Directory(workDir);
    if(! (await dir.exists())){
      await dir.create();
    }
    workDirPath = workDir;
    return workDir;
  }
}

在应用启动时调用解压将内置的 video-srt.zip 内容解压到系统 library 下的 videoSrt 目录下。

设置配置信息

video-srt的配置是用的 config.ini文件存储的,所以在代码里需要读写 ini 文件,这里使用了一个 ini的三方库,config.ini里包含如下配置内容:

#字幕相关设置
[srt]
#智能分段处理:true(开启) false(关闭)
intelligent_block=true
#阿里云Oss对象服务配置
#文档:https://help.aliyun.com/document_detail/31827.html?spm=a2c4g.11186623.6.582.4e7858a85Dr5pA
[aliyunOss]
# OSS 对外服务的访问域名
endpoint=
# 存储空间(Bucket)名称
bucketName=
# 存储空间(Bucket 域名)地址
bucketDomain=
accessKeyId=
accessKeySecret=
#阿里云语音识别配置
#文档:
[aliyunClound]
# 在管控台中创建的项目Appkey,项目的唯一标识
appKey=
accessKeyId=
accessKeySecret=

这里创建一个 ConfigModel用于存放相关配置,然后使用 ini 库的 Config 进行读写封装,代码如下 :

// 读取配置数据
static Future<ConfigModel> readIniData() async{
  var workDir = await PathUtils.getWorkDirPath();
  var iniPath = "$workDir/$VIDEO_SRT/$CONFIG_NAME";
  Completer<ConfigModel> completer = Completer();
  File(iniPath).readAsLines()
      .then((lines) => Config.fromStrings(lines))
      .then((Config config){
        var iniModel = ConfigModel();
        iniModel.intelligent_block = (config.get("srt", "intelligent_block") ?? "true").toLowerCase() == "true";
        iniModel.oss_endpoint = config.get("aliyunOss", "endpoint");
        iniModel.oss_bucketName = config.get("aliyunOss", "bucketName") ;
        iniModel.oss_bucketDomain = config.get("aliyunOss", "bucketDomain") ;
        iniModel.oss_accessKeyId = config.get("aliyunOss", "accessKeyId") ;
        iniModel.oss_accessKeySecret = config.get("aliyunOss", "accessKeySecret") ;
        iniModel.voice_appKey = config.get("aliyunClound", "appKey") ;
        iniModel.voice_accessKeyId = config.get("aliyunClound", "accessKeyId") ;
        iniModel.voice_accessKeySecret = config.get("aliyunClound", "accessKeySecret") ;
        iniModel.go_path = config.get("go", "goPath") ;
        completer.complete(iniModel);
  });
  return completer.future;
}
// 写配置数据
static Future<void> writeIniData(ConfigModel iniModel) async{
  Config config = Config();
  config.addSection("srt");
  config.set("srt", "intelligent_block", iniModel.intelligent_block.toString());
  config.addSection("aliyunOss");
  config.set("aliyunOss", "endpoint", iniModel.oss_endpoint ?? "");
  config.set("aliyunOss", "bucketName", iniModel.oss_bucketName ?? "");
  config.set("aliyunOss", "bucketDomain", iniModel.oss_bucketDomain ?? "");
  config.set("aliyunOss", "accessKeyId", iniModel.oss_accessKeyId ?? "");
  config.set("aliyunOss", "accessKeySecret", iniModel.oss_accessKeySecret ?? "");
  config.addSection("aliyunClound");
  config.set("aliyunClound", "appKey", iniModel.voice_appKey ?? "");
  config.set("aliyunClound", "accessKeyId", iniModel.voice_accessKeyId ?? "");
  config.set("aliyunClound", "accessKeySecret", iniModel.voice_accessKeySecret ?? "");
  config.addSection("go");
  config.set("go", "goPath", iniModel.go_path ?? "");
  var workDir = await PathUtils.getWorkDirPath();
  var iniPath = "$workDir/$VIDEO_SRT/$CONFIG_NAME";
  await File(iniPath).writeAsString(config.toString());
  return;
}

执行命令

配置也写好了,接下来就需要执行编译好的 video-srt 命令来提取视频字幕,这里使用 shell 命令来执行,用到了 process_run库,核心代码如下:

static Future<void> runVideoSrt(String targetFilePath, Function(String) callback) async{
  if(targetFilePath.isEmpty){
    return;
  }
  // 获取工作目录
  var workDir = await PathUtils.getWorkDirPath();
  var controller = ShellLinesController();
  var shell = Shell(stdout: controller.sink, verbose: false);
  // 切换路径到工作目录下的 video-srt 下
  shell = shell.pushd("$workDir/$VIDEO_SRT");
  try {
    // 给 ffmpeg 添加执行权限
    await shell.run("chmod +x ffmpeg");
    // 给 video-srt 添加执行权限
    await shell.run("chmod +x video-srt");
  } on ShellException catch (_) {
    // We might get a shell exception
  }
  // 监听执行结果
  controller.stream.listen((event) {
    callback(event);
  });
  try {
    // 执行视频提取字幕命令
    await shell.run("./video-srt $targetFilePath");
  } on ShellException catch (_) {
    // We might get a shell exception
  }
  shell = shell.popd();
  return;
}

UI 实现

核心功能实现了,接下来就是完成界面的开发,让我们可以方便的进行相关配置和选择要生成字幕的视频文件。

为了实现 Mac 风格的界面,这里使用了 macos_ui库,可以让我们更快捷的实现相关界面。

界面分成两部分,左边菜单和右边内容展示区域,效果如下:

代码如下:

class MainView extends StatefulWidget {
  const MainView({super.key});
  @override
  State<MainView> createState() => _MainViewState();
}
class _MainViewState extends State<MainView> {
  int _pageIndex = 0;
  @override
  Widget build(BuildContext context) {
    return PlatformMenuBar(
      menus: const [
        PlatformMenu(
          label: 'VideoSrtMacos',
          menus: [
            // 状态栏左上角退出按钮
            PlatformProvidedMenuItem(
              type: PlatformProvidedMenuItemType.quit,
            ),
          ],
        ),
      ],
      child:  MacosWindow(
        sidebar: Sidebar(
          minWidth: 200,
          builder: (context, scrollController) => SidebarItems(
            currentIndex: _pageIndex,
            onChanged: (index) {
              setState(() => _pageIndex = index);
            },
            items: const [
              SidebarItem(leading: MacosIcon(CupertinoIcons.home),label: Text('首页'),),
              SidebarItem(leading: MacosIcon(CupertinoIcons.settings),label: Text('配置'),),
              SidebarItem(leading: MacosIcon(CupertinoIcons.helm),label: Text('帮助'),),
              SidebarItem(leading: MacosIcon(CupertinoIcons.info),label: Text('关于'),),
            ],
          ),
        ),
        child: IndexedStack(
          index: _pageIndex,
          children: const [
            // 主页
            HomePage(),
            // 配置页面
            ConfigView(),
            HelpView(),
            AboutView()
          ],
        ),
      ),
    );
  }
}

然后分别实现对应的子界面即可实现整个完整的功能,这部分就是纯粹的 flutter 界面开发的内容了,这里就不过多赘述了。

最后

虽然使用 Flutter 进行开发已经很久了,但是更多还是进行 Android、iOS 的开发,桌面端虽然也写过一些Demo,但是还未真正使用 Flutter 去开发一个桌面应用,虽然这个项目功能很简单但也算是一个不错的练手项目。

Github 地址:video-srt-mac

以上就是Flutter开发Mac桌面应用实现自动提取生成视频字幕文件的详细内容,更多关于Flutter Mac提取视频字幕的资料请关注脚本之家其它相关文章!

相关文章

  • Android实现手机震动抖动效果的方法

    Android实现手机震动抖动效果的方法

    今天小编就为大家分享一篇关于Android实现手机震动抖动效果的方法,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2019-03-03
  • SurfaceView实现红包雨平移动画

    SurfaceView实现红包雨平移动画

    这篇文章主要为大家详细介绍了SurfaceView实现红包雨平移动画,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-07-07
  • Android图片无限轮播的实现代码

    Android图片无限轮播的实现代码

    这篇文章主要为大家详细介绍了Android图片无限轮播的实现代码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2016-12-12
  • Android仿iOS实现侧滑返回功能(类似微信)

    Android仿iOS实现侧滑返回功能(类似微信)

    这篇文章主要为大家详细介绍了Android仿iOS实现侧滑返回功能,类似微信功能,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-12-12
  • Android开发Flutter 桌面应用窗口化实战示例

    Android开发Flutter 桌面应用窗口化实战示例

    这篇文章主要为大家介绍了Android开发Flutter 桌面应用窗口化实战示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-09-09
  • Android手机上同时安装正式包与测试包的方法

    Android手机上同时安装正式包与测试包的方法

    这篇文章主要给大家介绍了关于Android手机上同时安装正式包与测试包的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-02-02
  • Android之获取手机内部及sdcard存储空间的方法

    Android之获取手机内部及sdcard存储空间的方法

    今天小编就为大家分享一篇Android之获取手机内部及sdcard存储空间的方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-08-08
  • 深入浅析Android消息机制

    深入浅析Android消息机制

    在Android中,线程内部或者线程之间进行信息交互时经常会使用消息,这些基础的东西如果我们熟悉其内部的原理,将会使我们容易、更好地架构系统,避免一些低级的错误,通过本文给大家介绍android消息机制,感兴趣的朋友一起学习吧
    2016-04-04
  • android自定义view实现推箱子小游戏

    android自定义view实现推箱子小游戏

    这篇文章主要为大家详细介绍了android自定义view实现推箱子小游戏,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-04-04
  • Android仿微信5实现滑动导航条

    Android仿微信5实现滑动导航条

    这篇文章主要为大家详细介绍了Android仿微信5实现滑动导航条,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2016-08-08

最新评论