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

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

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

前言

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

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

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

思路

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

效果

實(shí)現(xiàn)

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

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

首先將 CLI 項(xiàng)目 clone 到本地,然后使用 go build命令編譯對(duì)應(yīng)平臺(tái)的可執(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

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

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

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

然后就是通過(guò)代碼在程序啟動(dòng)時(shí)將內(nèi)置的壓縮包解壓到指定位置,這里解壓使用了 archive庫(kù),核心代碼如下:

// 目錄名稱
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庫(kù)用于獲取相關(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)用啟動(dòng)時(shí)調(diào)用解壓將內(nèi)置的 video-srt.zip 內(nèi)容解壓到系統(tǒng) library 下的 videoSrt 目錄下。

設(shè)置配置信息

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

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

這里創(chuàng)建一個(gè) ConfigModel用于存放相關(guān)配置,然后使用 ini 庫(kù)的 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í)行命令

配置也寫好了,接下來(lái)就需要執(zhí)行編譯好的 video-srt 命令來(lái)提取視頻字幕,這里使用 shell 命令來(lái)執(zhí)行,用到了 process_run庫(kù),核心代碼如下:

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)了,接下來(lái)就是完成界面的開發(fā),讓我們可以方便的進(jìn)行相關(guān)配置和選擇要生成字幕的視頻文件。

為了實(shí)現(xiàn) Mac 風(fēng)格的界面,這里使用了 macos_ui庫(kù),可以讓我們更快捷的實(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('首頁(yè)'),),
              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 [
            // 主頁(yè)
            HomePage(),
            // 配置頁(yè)面
            ConfigView(),
            HelpView(),
            AboutView()
          ],
        ),
      ),
    );
  }
}

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

最后

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

Github 地址:video-srt-mac

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

相關(guān)文章

最新評(píng)論