使用 nodejs 開發命令行小工具 - 谷歌翻譯字幕

使用 nodejs 能夠很是方便的開發命令行工具,來解決咱們遇到的一些問題。javascript

如今就讓咱們看看如何使用 nodejs 開發一個把 .srt 格式的字幕文件翻譯成中文和外語的雙語字幕,而後在把它發佈到 npm 倉庫中。html

準備

在安裝好 nodejs 環境後,進入到項目目錄後使用vue

npm init -y
複製代碼

來,建立 package.json 文件,而後我選擇把主文件放入 src 下。java

├── package.json
└── src
    └── fysrt.js
複製代碼

而後咱們須要安裝以下依賴node

commander.js

commander.js 能夠幫助咱們解析命令行參數和註冊子命令,顯示幫助信息,版本號。。。git

var program = require('commander');

program
  .version('0.1.0')
  .option('-f, --foo', 'enable some foo')
  .option('-b, --bar', 'enable some bar')
  .option('-B, --baz', 'enable some baz');

program.on('--help', function(){
  console.log('')
  console.log('Examples:');
  console.log(' $ custom-help --help');
  console.log(' $ custom-help -h');
});

program.parse(process.argv);
複製代碼
Usage: custom-help [options]

Options:
  -h, --help     output usage information
  -V, --version  output the version number
  -f, --foo      enable some foo
  -b, --bar      enable some bar
  -B, --baz      enable some baz

Examples:
  $ custom-help --help
  $ custom-help -h
複製代碼

Inquirer.js

Inquirer.js 可讓命令行與用戶進行交互。github

signale

signale 能夠用來打印信息到屏幕npm

fs-extra 和 klaw

fs-extra 是對 fs 的包裝,它提供了 promise 支持,還有一些有用的功能。json

klaw 本來屬於 fs-extra 的一個功能,可是如今它被抽離出來,它能夠用來遍歷目錄。api

translate-google-cn

translate-google-cn 是我把 google-translate-api 稍微改了一下。

  • google.com 變成 google.cn
  • 修改了獲取 token 的正則(原來的不起做用了)。
  • 添加了 cookie ,這樣更不容易被 google 封 ip

更多

想要了解更多的命令行工具能夠參考 這裏

執行腳本

如今咱們可使用 $ node src/fysrt.js 來執行這個文件,可是這很麻煩,咱們想使用 $ fysrt 來直接執行這個文件。

首先咱們在文件開頭加入

#!/usr/bin/env node
複製代碼

不加的話咱們的腳本文件,就不會使用 node 執行它。

bin

而後咱們在 package.json 中加入 bin 字段

使用 bin 字段能夠將命令名和文件名映射,在安裝時 npm 會將咱們的可執行文件符號連接到 {prefix}/bin (全局安裝)或 ./node_modules/.bin/ 本地安裝,這樣咱們就不用輸入路徑來執行文件了。

{
    "name": "fysrt",
    "main": "src/fysrt.js",
    "bin": "src/fysrt.js"
}
複製代碼

bin 是一個字符串時,表明命令名與包名同名。

它還能夠安裝多個命令。

{
  "bin": {
    "c1": "bin/c1.js",
    "c2": "bin/c2.js"
  }
}
複製代碼

這樣安裝就有 c1 與 c2 兩個命令。

npm link

咱們想讓上面設置的 bin 起做用,能夠發佈和安裝包,npm 纔會幫咱們作符號連接,可是這樣太麻煩,咱們還可使用 npm link 命令。

它能夠簡寫爲 npm ln,咱們直接去項目目錄執行 npm link 就能夠了。

它會根據 package.json 的配置,在 {prefix}/lib/node_modules/<package> 中建立一個符號連接,它還會將包中的任何 bin 文件連接到 {prefix}/bin/{name}

若是咱們想把它看成一個普通的包使用,咱們能夠去要用到它的項目文件夾,執行 npm link fysrt,它會在該項目文件夾下的 node_modules 中連接到全局的 fysrt。咱們對 fysrt 的修改均可以直接映射到該項目的 fysrt。

當咱們想取消連接時能夠執行 npm unlink fysrt

srt 字幕文件

srt 字幕文件中的一句字幕,分爲三部分。

650
00:45:07,650 --> 00:45:09,110
Fifteen minutes.

651
00:45:10,650 --> 00:45:20,110
Fifteen minutes.Fifteen minutes.Fifteen minutes.
複製代碼

索引編號,時間,和字幕。字幕前面可能會有一些特效代碼,如 {\an6} 等等命令,或者還有 html 形式的。

每句字幕使用兩個換行符分隔。

代碼編寫

咱們使用 commander.js 來處理命令行參數。

commander
  .version(version)
  .option('-d, --delete', '刪除原文件')
  .option('-s, --single', '單語字幕,而不是雙語字幕')
  .option('-f, --from <lang>', '原始語言,默認 auto')
  .option('-t, --to <lang>', '翻譯成什麼語言,默認 zh-cn')
  .option(
    '-T, --time <time>',
    '每一個字幕文件的翻譯時間間隔 毫秒,默認 3000 毫秒'
  )
  .option(
    '-S, --size <size>',
    '一次給 google api 翻譯的文本量,默認一次 50 行字幕'
  )
  .on('--help', () => {
    console.log();
    console.log('Examples:');
    console.log(' $ fysrt ./subtitles');
    console.log(' $ fysrt -d a.srt');
    console.log(' $ fysrt -f en a.srt');
  })
  .parse(process.argv);
複製代碼

而後咱們可使用 commander.args[0] 獲取到輸入的 目錄或者字幕文件。

當沒有目錄或文件時,咱們能夠提示是否翻譯當前目錄下的全部字幕文件。

const ans = await inquirer.prompt([
  {
    type: 'confirm',
    name: 'dir',
    message: '翻譯當前文件夾下的全部字幕文件?',
    default: false
  }
]);

if (!ans.dir) return;
複製代碼

若是是文件夾的話,咱們使用 klaw 遍歷目錄,找到全部 srt 文件。

const files = [];
walk(target)
  .on('data', ({ path: p }) => p && p.endsWith('.srt') && files.push(p))
  .on('end', async () => {
    const len = files.length;
    if (len === 0) {
      signale.error(`目錄下沒有 .srt 文件 -> ${target}`);
      process.exit(1)
    }
    
    // ...
  });
複製代碼

而後咱們讀取字幕文件而後解析它,因爲有些 srt 字幕文件不嚴格符合規範, 因此須要一行一行的判斷這一行是時間仍是字幕。

const lines = rawData.trim().split(/(?:\r\n|\n|\r)/); // 獲取全部行
const data = [];

for (let i = 0, len = lines.length; i < len; i++) {
  let l = lines[i].trim();
  // eslint-disable-next-line eqeqeq
  if (!l || ~~l !== 0 || l == 0) continue; // 若是是空行或者是編號行則跳過

  if (/^(?:\d+:){2}\d+,\d+\s-->\s(?:\d+:){2}\d+[,.]\d+$/.test(l)) {
    data.push([l]); // 處理時間行
  } else if (/^\d+:\d+\.\d+\s-->\s\d+:\d+\.\d+$/.test(l)) {
    data.push([ // 處理 vtt 文件格式的時間行
      l
        .replace(/\./g, ',')
        .split(' --> ')
        .map(s => '00:' + s)
        .join(' --> ')
    ]);
  } else { // 處理字幕行
    l = l.replace(/^(?:\{\\\w.*\})+/, ''); // 去除特效代碼
    let last = data[data.length - 1];
    if (last.length === 1) {
      last.push(l);
    } else {
      last[1] = last[1] + '\n' + l;
    }
  }
}
複製代碼

而後咱們就使用谷歌翻譯

const requests = [];
for (let i = 0, len = textArr.length; i <= len; i += size) {

// textArr 就是上面 data 的 data.map(d => d[1]),size 是上面命令行傳入的參數,默認 50 行
// 由於翻譯是 get 請求,一次性太多文字,谷歌服務器會報 413 錯

    requests.push(
      translate(textArr.slice(i, i + size).join('\n\n'), {
        from,
        to
      })
    );
}

const res = await Promise.all(requests); // 併發的去翻譯
複製代碼

最後把獲得的翻譯組合起來,而後寫入到文件中就能夠了。

const translate = res
      .map(r => r.text.split('\n\n'))
      .reduce((acc, val) => {
        acc.push(...val);
        return acc;
      }, []);
data
    .map(
      (d, i) =>
        `${i + 1}\n${d[0]}\n${translate[i]}${keep ? '\n' + d[1] : ''}`
    )
    .join('\n\n') + '\n\n'
複製代碼

源碼

上面的代碼只是這個小工具的核心部分,

完整的代碼能夠參考 github 倉庫

發佈 npm 包

npm 包分爲 unscoped 和 scoped,unscoped 就是咱們常見的 npm 包,scoped 就是包前面有一個 @ 符號的包好比 @vue/cli

scoped 包能夠分爲團體和我的。

scoped 的包默認是私有的,但須要付費。可修改 package.json 文件讓它是公開的。

要發佈包到 npm 咱們首先要註冊一個 npm 賬號。

而後登入帳戶

npm login
複製代碼

再發布包

npm publish
複製代碼

這樣就能夠了。可是有可能報錯,好比倉庫中已經有這個包名了,這時只有換一個名字,或者發佈 scoped 包。

咱們能夠修改 package.json

{
    "name": "@npm帳戶名稱/包名"
}
複製代碼

帳戶名能夠經過

npm whoami
複製代碼

查詢。

而後咱們在發佈公共包

npm publish --access public
複製代碼

迭代包

咱們可使用 npm version 命令遞增版本號。

npm 版本號是 major.minor.patch 主版本.次版本.補丁版本。

npm version patch
複製代碼

咱們去查看 package.json 就會發現 version 字段改變了。

而後再發布包

npm publish
複製代碼

廢棄 刪除 包

咱們能夠廢棄一個包的版本或者整個包。

npm deprecate <pkg>[@<version>] <message>
複製代碼

npm 不建議刪除包,由於包可能被別人引用。因此 npm 作了限制

  • 刪除的版本 24 小時後方可重發
  • 包發佈 72 小時以內纔可刪除
npm unpublish pkg --force
複製代碼
相關文章
相關標籤/搜索