欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Flutter開發(fā)Mac桌面應(yīng)用實(shí)現(xiàn)自動提取生成視頻字幕文件

 更新時間:2023年03月29日 14:41:57   作者:loongwind  
這篇文章主要為大家介紹了Flutter開發(fā)Mac桌面應(yīng)用實(shí)現(xiàn)自動提取生成視頻字幕文件示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

前言

前段時間準(zhǔn)備做一個視頻,最后需要添加字幕,手動添加太麻煩了就想在網(wǎng)上找一個能自動提取字幕的軟件或服務(wù),確實(shí)是找到了,但是免費(fèi)版基本上都有諸多限制,比如現(xiàn)在視頻時長等等,后來在 Github 找到一個開源的版本是使用云平臺的語音識別實(shí)現(xiàn)的,云服務(wù)的語音識別是有免費(fèi)的額度的,對于個人使用來說一般是夠用了,項(xiàng)目地址:video-srt-windows ,大致實(shí)現(xiàn)流程如下:

  • 使用 ffmpeg 提取視頻的音頻文件
  • 將音頻文件上傳到云平臺的對象存儲
  • 調(diào)用云平臺的語音識別 api 進(jìn)行文字識別
  • 生成字幕文件

下載 release 版本測試了一下效果還可以,只需要修改個別識別有誤的詞就行,功能完全滿足我的需求;但是遺憾的是該項(xiàng)目只提供了 Windows 版本,而沒有 Mac 版本的 ,雖然作者也提供了一個 CLI 命令行版本可以在 Mac 上使用,但是對于普通用戶來說使用起來還是不是很方便,于是產(chǎn)生了開發(fā)一個 Mac 版。

思路

該開源項(xiàng)目作者是用 Go 語言寫的,我本人擅長的是 Flutter 開發(fā),所以首先想到的就是通過 Flutter 開發(fā)一個 Mac 版的桌面應(yīng)用,將 CLI 項(xiàng)目通過 Go 編譯成 Mac 的可執(zhí)行文件內(nèi)置到 Flutter 項(xiàng)目中,再通過 Dart 調(diào)用 shell 命令進(jìn)行執(zhí)行從而實(shí)現(xiàn)軟件的功能。

效果

實(shí)現(xiàn)

下面就來看看整個項(xiàng)目是如何一步步最終實(shí)現(xiàn)上面的效果的。

編譯 Mac 版可執(zhí)行文件

首先將 CLI 項(xiàng)目 clone 到本地,然后使用 go build命令編譯對應(yīng)平臺的可執(zhí)行文件,如下:

# Mac M1/M2 Arm 架構(gòu) CPU
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -o video-srt-arm64 main.go
# Mac Amd 架構(gòu) CPU
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o video-srt-amd64 main.go

執(zhí)行以上文件分別生成 armamd 架構(gòu)的可執(zhí)行文件 video-srt-arm64 和 video-srt-amd64。

內(nèi)置可執(zhí)行文件和 ffmpeg

將上一步生成的對應(yīng)平臺的可執(zhí)行文件修改為 video-srt 和配置文件 config.ini以及 ffmpeg文件放到一個文件夾中打包成 video-srt.zip壓縮包減少包體積。

因?yàn)轫?xiàng)目需要使用到 ffmpeg ,所以需要把 ffmpeg 也內(nèi)置到項(xiàng)目中

通過 Xcode 將 video-srt.zip文件添加到項(xiàng)目的 Resources 文件夾下

然后就是通過代碼在程序啟動時將內(nèi)置的壓縮包解壓到指定位置,這里解壓使用了 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();
    // 創(chuàng)建工作目錄下的 video-srt 目錄
    var videoSrtFile = Directory("$workDirPath/$VIDEO_SRT");
    // 如果已經(jīng)存在則不重復(fù)解壓
    if(await videoSrtFile.exists()){
      return;
    }
    // 解壓
    await unzip(VIDEO_SRT_ZIP_PATH, "$workDirPath");
    return;
  }
}

這里還用到了 path_provider庫用于獲取相關(guān)目錄:

// 工作目錄名稱
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;
  }
}

在應(yīng)用啟動時調(diào)用解壓將內(nèi)置的 video-srt.zip 內(nèi)容解壓到系統(tǒng) library 下的 videoSrt 目錄下。

設(shè)置配置信息

video-srt的配置是用的 config.ini文件存儲的,所以在代碼里需要讀寫 ini 文件,這里使用了一個 ini的三方庫,config.ini里包含如下配置內(nèi)容:

#字幕相關(guān)設(shè)置
[srt]
#智能分段處理:true(開啟) false(關(guān)閉)
intelligent_block=true
#阿里云Oss對象服務(wù)配置
#文檔:https://help.aliyun.com/document_detail/31827.html?spm=a2c4g.11186623.6.582.4e7858a85Dr5pA
[aliyunOss]
# OSS 對外服務(wù)的訪問域名
endpoint=
# 存儲空間(Bucket)名稱
bucketName=
# 存儲空間(Bucket 域名)地址
bucketDomain=
accessKeyId=
accessKeySecret=
#阿里云語音識別配置
#文檔:
[aliyunClound]
# 在管控臺中創(chuàng)建的項(xiàng)目Appkey,項(xiàng)目的唯一標(biāo)識
appKey=
accessKeyId=
accessKeySecret=

這里創(chuàng)建一個 ConfigModel用于存放相關(guān)配置,然后使用 ini 庫的 Config 進(jìn)行讀寫封裝,代碼如下 :

// 讀取配置數(shù)據(jù)
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;
}
// 寫配置數(shù)據(jù)
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;
}

執(zhí)行命令

配置也寫好了,接下來就需要執(zhí)行編譯好的 video-srt 命令來提取視頻字幕,這里使用 shell 命令來執(zhí)行,用到了 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 添加執(zhí)行權(quán)限
    await shell.run("chmod +x ffmpeg");
    // 給 video-srt 添加執(zhí)行權(quán)限
    await shell.run("chmod +x video-srt");
  } on ShellException catch (_) {
    // We might get a shell exception
  }
  // 監(jiān)聽執(zhí)行結(jié)果
  controller.stream.listen((event) {
    callback(event);
  });
  try {
    // 執(zhí)行視頻提取字幕命令
    await shell.run("./video-srt $targetFilePath");
  } on ShellException catch (_) {
    // We might get a shell exception
  }
  shell = shell.popd();
  return;
}

UI 實(shí)現(xiàn)

核心功能實(shí)現(xiàn)了,接下來就是完成界面的開發(fā),讓我們可以方便的進(jìn)行相關(guān)配置和選擇要生成字幕的視頻文件。

為了實(shí)現(xiàn) Mac 風(fēng)格的界面,這里使用了 macos_ui庫,可以讓我們更快捷的實(shí)現(xiàn)相關(guān)界面。

界面分成兩部分,左邊菜單和右邊內(nèi)容展示區(qū)域,效果如下:

代碼如下:

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: [
            // 狀態(tài)欄左上角退出按鈕
            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('關(guān)于'),),
            ],
          ),
        ),
        child: IndexedStack(
          index: _pageIndex,
          children: const [
            // 主頁
            HomePage(),
            // 配置頁面
            ConfigView(),
            HelpView(),
            AboutView()
          ],
        ),
      ),
    );
  }
}

然后分別實(shí)現(xiàn)對應(yīng)的子界面即可實(shí)現(xiàn)整個完整的功能,這部分就是純粹的 flutter 界面開發(fā)的內(nèi)容了,這里就不過多贅述了。

最后

雖然使用 Flutter 進(jìn)行開發(fā)已經(jīng)很久了,但是更多還是進(jìn)行 Android、iOS 的開發(fā),桌面端雖然也寫過一些Demo,但是還未真正使用 Flutter 去開發(fā)一個桌面應(yīng)用,雖然這個項(xiàng)目功能很簡單但也算是一個不錯的練手項(xiàng)目。

Github 地址:video-srt-mac

以上就是Flutter開發(fā)Mac桌面應(yīng)用實(shí)現(xiàn)自動提取生成視頻字幕文件的詳細(xì)內(nèi)容,更多關(guān)于Flutter Mac提取視頻字幕的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評論