使用 nodejs 开发命令行小工具 - 谷歌翻译字幕

2022-10-08 13:49:26 浏览数 (1)

使用 nodejs 可以非常方便的开发命令行工具,来解决我们遇到的一些问题。

现在就让我们看看如何使用 nodejs 开发一个把 .srt 格式的字幕文件翻译成中文和外语的双语字幕,然后在把它发布到 npm 仓库中。

准备

在安装好 nodejs 环境后,进入到项目目录后使用

代码语言:javascript复制
npm init -y

来,创建 package.json 文件,然后我选择把主文件放入 src 下。

代码语言:javascript复制
├── package.json└── src
    └── fysrt.js

然后我们需要安装如下依赖

commander.js

commander.js 可以帮助我们解析命令行参数和注册子命令,显示帮助信息,版本号。。。

代码语言:javascript复制
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);
代码语言:javascript复制
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 bazExamples:
  $ custom-help --help
  $ custom-help -h

Inquirer.js

Inquirer.js 可以让命令行与用户进行交互。

signale

signale 可以用来打印信息到屏幕

fs-extra 和 klaw

fs-extra 是对 fs 的包装,它提供了 promise 支持,还有一些有用的功能。

klaw 原本属于 fs-extra 的一个功能,但是现在它被抽离出来,它可以用来遍历目录。

translate-google-cn

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

  • google.com 变成 google.cn
  • 修改了获取 token 的正则(原来的不起作用了)。
  • 添加了 cookie ,这样更不容易被 google 封 ip

更多

想要了解更多的命令行工具可以参考 这里

执行脚本

现在我们可以使用 node src/fysrt.js 来执行这个文件,但是这很麻烦,我们想使用 fysrt 来直接执行这个文件。

首先我们在文件开头加入

代码语言:javascript复制
#!/usr/bin/env node

不加的话我们的脚本文件,就不会使用 node 执行它。

bin

然后我们在 package.json 中加入 bin 字段

使用 bin 字段可以将命令名和文件名映射,在安装时 npm 会将我们的可执行文件符号链接到 {prefix}/bin (全局安装)或 ./node_modules/.bin/ 本地安装,这样我们就不用输入路径来执行文件了。

代码语言:javascript复制
{
    "name": "fysrt",
    "main": "src/fysrt.js",
    "bin": "src/fysrt.js"}

bin 是一个字符串时,代表命令名与包名同名。

它还可以安装多个命令。

代码语言:javascript复制
{
  "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 字幕文件中的一句字幕,分为三部分。

代码语言:javascript复制
65000:45:07,650 --> 00:45:09,110Fifteen minutes.65100:45:10,650 --> 00:45:20,110Fifteen minutes.Fifteen minutes.Fifteen minutes.

索引编号,时间,和字幕。字幕前面可能会有一些特效代码,如 {an6} 等等命令,或者还有 html 形式的。

每句字幕使用两个换行符分隔。

代码编写

我们使用 commander.js 来处理命令行参数。

代码语言:javascript复制
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] 获取到输入的 目录或者字幕文件。

当没有目录或文件时,我们可以提示是否翻译当前目录下的所有字幕文件。

代码语言:javascript复制
const ans = await inquirer.prompt([
  {    type: 'confirm',    name: 'dir',    message: '翻译当前文件夹下的所有字幕文件?',    default: false
  }
]);if (!ans.dir) return;

如果是文件夹的话,我们使用 klaw 遍历目录,找到所有 srt 文件。

代码语言:javascript复制
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 字幕文件不严格符合规范, 所以需要一行一行的判断这一行是时间还是字幕。

代码语言:javascript复制
const lines = rawData.trim().split(/(?:rn|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-->sd :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;
    }
  }
}

然后我们就使用谷歌翻译

代码语言:javascript复制
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('nn'), {        from,
        to
      })
    );
}const res = await Promise.all(requests); // 并发的去翻译

最后把得到的翻译组合起来,然后写入到文件中就可以了。

代码语言:javascript复制
const translate = res
      .map(r => r.text.split('nn'))
      .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('nn')   'nn'

源码

上面的代码只是这个小工具的核心部分,

完整的代码可以参考 github 仓库

发布 npm 包

npm 包分为 unscoped 和 scoped,unscoped 就是我们常见的 npm 包,scoped 就是包前面有一个 @ 符号的包比如 @vue/cli

scoped 包可以分为团体和个人。

scoped 的包默认是私有的,但需要付费。可修改 package.json 文件让它是公开的。

要发布包到 npm 我们首先要注册一个 npm 帐号。

然后登入账户

代码语言:javascript复制
npm login

再发布包

代码语言:javascript复制
npm publish

这样就可以了。但是有可能报错,比如仓库中已经有这个包名了,这时只有换一个名字,或者发布 scoped 包。

我们可以修改 package.json

代码语言:javascript复制
{
    "name": "@npm账户名称/包名"}

账户名可以通过

代码语言:javascript复制
npm whoami

查询。

然后我们在发布公共包

代码语言:javascript复制
npm publish --access public

迭代包

我们可以使用 npm version 命令递增版本号。

npm 版本号是 major.minor.patch 主版本.次版本.补丁版本。

代码语言:javascript复制
npm version patch

我们去查看 package.json 就会发现 version 字段改变了。

然后再发布包

代码语言:javascript复制
npm publish

废弃 删除 包

我们可以废弃一个包的版本或者整个包。

代码语言:javascript复制
npm deprecate <pkg>[@<version>] <message>

npm 不建议删除包,因为包可能被别人引用。所以 npm 做了限制

  • 删除的版本 24 小时后方可重发
  • 包发布 72 小时之内才可删除
代码语言:javascript复制
npm unpublish pkg --force

0 人点赞