批量提取某音文案

298 阅读5分钟

牙叔教程 简单易懂

我想学习某个人的文案, 怎么把它的文案全下载下来?

  1. 批量下载视频和音频
  2. 批量音频转文字

下载视频和音频

我在github找到的是这个仓库

github.com/Johnserf-Se…

经过实际测试, 可以使用, 只是失败的频率略高.

获取用户信息的时候, 尝试了5次才正确获取到用户信息,

只有获取到用户信息, 才能下载到视频;

虽然失败的概率有点高, 但是也仅是要多按几下 ↑ 和 回车键, 也不费啥事.

如果, 填写了cookie, 那么成功率大大提高, 不懂的话就看仓库的 issue.

这是下载好的视频和音频, 可以看到mp4和mp3的后缀名,

音频是直接下载下来的, 不是后期提取的.

文件 conf.ini 默认是不下载音频的, 所以要改一下.

music后面默认是no, 改成yes

[music]
# 视频原声保存(yes|no)
music = yes

其他的就照着README.md做就可以了


语音转文字

一共三种方案

  • 某讯
  • 微软
  • 有道

某讯

我看了一下某讯的价格, 70块30个小时,


微软

微软也有语音转文字的服务, 就是要绑卡, 绑卡的话, 某宝是几十块左右;


网易

网易, 他是2块钱一个小时, 这个就很人性化,

某讯开始就是30个小时, 我又用不了那么多, 至少我现在用不了.

批量自动化的时候, 你才需要买它, 你单独转的话, 可以直接使用

网易见外 jianwai.youdao.com/index/0

网易见外不用花一分钱, 但是依然很好用


那么今天给大家试试两种方案

微软

如果你没有卡, 可以去某宝搞;

如果你不会创建微软的服务, 可以看这个教程

文本转语音-微软Azure-一步一步教你从注册到使用

mp.weixin.qq.com/s/ycc35s51W…


我们要把音频转文字, 所以选择第二个, 脱机字幕

他的文档有代码, 直接复制黏贴就可以使用了

代码在此

learn.microsoft.com/zh-cn/azure…

下面是我的代码例子

语音转文字, 分两步

  • mp3转wav
  • wav转文字
// 此示例支持最多 30 秒的音频。
const fs = require("fs");
const sdk = require("microsoft-cognitiveservices-speech-sdk");
const { exec } = require("child_process");

const { secretKey: YourSubscriptionKey, region: YourServiceRegion, mp3FilePath } = require("./config.js");

let wavFilePath = mp3FilePath.replace(".mp3", ".wav");
let mp3ToWavCmd = `ffmpeg -i "${mp3FilePath}" "${wavFilePath}"`;

const speechConfig = sdk.SpeechConfig.fromSubscription(YourSubscriptionKey, YourServiceRegion);
speechConfig.speechRecognitionLanguage = "zh-CN";

async function recognizeSpeechFromFile() {
  let speechRecognizer;
  try {
    // Convert MP3 to WAV
    await new Promise((resolve, reject) => {
      exec(mp3ToWavCmd, (error) => {
        if (error) {
          console.error(`Error converting MP3 to WAV: ${error.message}`);
          reject(error);
        } else {
          console.log("MP3 file converted to WAV successfully.");
          resolve();
        }
      });
    });

    const audioConfig = sdk.AudioConfig.fromWavFileInput(fs.readFileSync(wavFilePath));
    speechRecognizer = new sdk.SpeechRecognizer(speechConfig, audioConfig);
    await new Promise((resolve, reject) => {
      speechRecognizer.recognizeOnceAsync((result) => {
        switch (result.reason) {
          case sdk.ResultReason.RecognizedSpeech:
            console.log(`RECOGNIZED: Text=${result.text}`);
            resolve(result.text);
            break;
          case sdk.ResultReason.NoMatch:
            console.log("NOMATCH: Speech could not be recognized.");
            reject("NOMATCH");
            break;
          case sdk.ResultReason.Canceled: {
            const cancellation = sdk.CancellationDetails.fromResult(result);
            console.log(`CANCELED: Reason=${cancellation.reason}`);

            if (cancellation.reason === sdk.CancellationReason.Error) {
              console.log(`CANCELED: ErrorCode=${cancellation.ErrorCode}`);
              console.log(`CANCELED: ErrorDetails=${cancellation.errorDetails}`);
              console.log("CANCELED: Did you set the speech resource key and region values?");
            }
            reject("CANCELED");
            break;
          }
        }
      });
    });
  } catch (error) {
    console.error("Error during recognition:", error);
  } finally {
    if (speechRecognizer) {
      speechRecognizer.close();
    }
  }
}

recognizeSpeechFromFile();

不过, 还是应该看看文档再动手,

这是微软文字转语音的文档

learn.microsoft.com/zh-cn/azure…


上面的代码只支持30s, 如果改成这种订阅式的代码, 就支持更长时间了

我们将订阅从 SpeechRecognizer 发送的事件:

recognizing:事件信号,包含中间识别结果。
recognized:包含最终识别结果的事件信号,指示成功的识别尝试。
sessionStopped:事件信号,指示识别会话的结束(操作)。
canceled:事件信号,包含已取消的识别结果。 这些结果指示因直接取消请求而取消的识别尝试。 或者,它们指示传输或协议失败。
speechRecognizer.recognizing = (s, e) => {
    console.log(`RECOGNIZING: Text=${e.result.text}`);
};

speechRecognizer.recognized = (s, e) => {
    if (e.result.reason == sdk.ResultReason.RecognizedSpeech) {
        console.log(`RECOGNIZED: Text=${e.result.text}`);
    }
    else if (e.result.reason == sdk.ResultReason.NoMatch) {
        console.log("NOMATCH: Speech could not be recognized.");
    }
};

speechRecognizer.canceled = (s, e) => {
    console.log(`CANCELED: Reason=${e.reason}`);

    if (e.reason == sdk.CancellationReason.Error) {
        console.log(`"CANCELED: ErrorCode=${e.errorCode}`);
        console.log(`"CANCELED: ErrorDetails=${e.errorDetails}`);
        console.log("CANCELED: Did you set the speech resource key and region values?");
    }

    speechRecognizer.stopContinuousRecognitionAsync();
};

speechRecognizer.sessionStopped = (s, e) => {
    console.log("\n    Session stopped event.");
    speechRecognizer.stopContinuousRecognitionAsync();
};

下载的数据

mp4 + mp3 + txt

语音转的文字

这是一个5分钟的电影解说, 1412个字,

如果是小说, 估计字数得翻倍, 这个是电影, 有一些时间是放的电影片段, 没有台词.


ffmpge

由于微软默认支持wav, 所以我们需要转换语音格式, 把mp3转成wav,

因此, 需要在电脑上安装 FFMPEG, 不会装的可以百度 Windows 10系统下安装FFmpeg教程详解


ffmpeg使用注意事项

  • 如果文件存在, 那么命令会卡住

网易

长语音转写文档

ai.youdao.com/DOCSIRMA/ht…


创建应用时, 选择 长语音转写


网易文档中的错误

这个sliceId是 大写的i (爱),

但是他这个文档里是小写的l (矮楼)


文档中的响应结果, 是写文档的人复制黏贴的.

比如, 这几张响应结果, msg都是sucess


实际上, msg的值都是 null


文件上传的时候, 这里是字节数组, 而不是text

你就说这样写文档快不快吧, 类型全是text


网易长语音转文本的代码

const fs = require("fs");
const axios = require("axios");
const path = require("path");
const crypto = require("crypto");
const FormData = require("form-data");
const util = require("util");
const { wangyi } = require("./config.js");
const stat = util.promisify(fs.stat);

async function getFileSize(filePath) {
  try {
    const stats = await stat(filePath);
    const fileSizeInBytes = stats.size;
    console.log(`文件大小: ${fileSizeInBytes} 字节`);
    return fileSizeInBytes;
  } catch (err) {
    console.error("发生错误:", err);
  }
}

async function wangYiRcognizeSpeechFromFile(audioFilePath) {
  let fileName = path.basename(audioFilePath);
  // 请在这里设置您的应用ID和应用密钥
  const appKey = wangyi.appKey;
  const appSecret = wangyi.secretKey;
  let fileSize = await getFileSize(audioFilePath);
  let salt = uuidv4();
  let curtime = Math.floor(Date.now() / 1000);
  let sign = crypto
    .createHash("sha256")
    .update(appKey + salt + curtime + appSecret)
    .digest("hex");

  let formData = new FormData();
  formData.append("salt", salt);
  formData.append("type", 1);
  formData.append("appKey", appKey);
  formData.append("sliceNum", "1");
  formData.append("name", fileName);
  formData.append("fileSize", fileSize);
  formData.append("curtime", curtime);
  formData.append("langType", "zh-CHS");
  formData.append("sign", sign);
  formData.append("signType", "v4");
  formData.append("format", "mp3");
  formData.append("noitn", 1);

  const prepareResponse = await axios.post("http://openapi.youdao.com/api/audio/prepare", formData, { headers: formData.getHeaders() });

  if (prepareResponse.data.errorCode !== "0") {
    throw new Error(`Prepare failed: ${prepareResponse.data.msg}`);
  }
  console.log("准备上传");
  console.log(prepareResponse.data);
  const taskid = prepareResponse.data.result;
  formData = new FormData();
  formData.append("q", taskid);
  formData.append("appKey", appKey);
  salt = uuidv4();

  formData.append("salt", salt);
  formData.append("curtime", curtime);

  sign = crypto
    .createHash("sha256")
    .update(appKey + salt + curtime + appSecret)
    .digest("hex");

  formData.append("sign", sign);
  formData.append("signType", "v4");
  formData.append("sliceId", "1");
  // 判断文件是否存在
  if (!fs.existsSync(audioFilePath)) {
    throw new Error(`File not found: ${audioFilePath}`);
  }
  // 打印文件大小
  console.log(`文件大小: ${fs.statSync(audioFilePath).size} 字节`);
  formData.append("file", fs.readFileSync(audioFilePath), { contentType: "audio/mp3", filename: fileName });
  formData.append("type", "1");

  const uploadResponse = await axios.post("http://openapi.youdao.com/api/audio/upload", formData, { headers: formData.getHeaders() });

  if (uploadResponse.data.errorCode !== "0") {
    throw new Error(`Upload failed: ${uploadResponse.data.msg}`);
  }
  console.log("上传文件");
  console.log(uploadResponse.data);
  /* -------------------------------------------------------------------------- */
  formData = new FormData();
  formData.append("q", taskid);
  formData.append("appKey", appKey);
  salt = uuidv4();
  formData.append("salt", salt);
  curtime = Math.floor(Date.now() / 1000);
  formData.append("curtime", curtime);
  sign = crypto
    .createHash("sha256")
    .update(appKey + salt + curtime + appSecret)
    .digest("hex");

  formData.append("sign", sign);
  formData.append("signType", "v4");

  const mergeResponse = await axios.post("http://openapi.youdao.com/api/audio/merge", formData, { headers: formData.getHeaders() });

  if (mergeResponse.data.errorCode !== "0") {
    throw new Error(`Merge failed: ${mergeResponse.data.msg}`);
  }
  console.log("合并文件");
  console.log(mergeResponse.data);
  // process.exit(0);

  let progressResponse;
  do {
    await new Promise((resolve) => setTimeout(resolve, 60 * 1000));
    formData = new FormData();
    formData.append("q", taskid);
    formData.append("appKey", appKey);
    salt = uuidv4();
    formData.append("salt", salt);
    curtime = Math.floor(Date.now() / 1000);
    formData.append("curtime", curtime);
    sign = crypto
      .createHash("sha256")
      .update(appKey + salt + curtime + appSecret)
      .digest("hex");

    formData.append("sign", sign);
    formData.append("signType", "v4");
    progressResponse = await axios.post("http://openapi.youdao.com/api/audio/get_progress", formData, { headers: formData.getHeaders() });
    console.log("获取进度");
    console.log(progressResponse.data);
  } while (progressResponse.data.result[0].status !== "9");

  formData = new FormData();
  formData.append("q", taskid);
  formData.append("appKey", appKey);
  salt = uuidv4();
  formData.append("salt", salt);
  curtime = Math.floor(Date.now() / 1000);
  formData.append("curtime", curtime);
  sign = crypto
    .createHash("sha256")
    .update(appKey + salt + curtime + appSecret)
    .digest("hex");

  formData.append("sign", sign);
  formData.append("signType", "v4");

  const resultResponse = await axios.post("http://openapi.youdao.com/api/audio/get_result", formData, { headers: formData.getHeaders() });

  if (resultResponse.data.errorCode !== "0") {
    throw new Error(`Get result failed: ${resultResponse.data.msg}`);
  }
  console.log("获取结果");
  console.log(resultResponse.data);
  const text = resultResponse.data.result.map((item) => item.sentence).join("\n");
  fs.writeFileSync(fileName, text);
}

// 生成 UUID v4
function uuidv4() {
  return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
    var r = (Math.random() * 16) | 0,
      v = c == "x" ? r : (r & 0x3) | 0x8;
    return v.toString(16);
  });
}

网易与微软对比

微软网易
获取结果需要轮询不需要需要
准确度略高略低
接口方便程度一个订阅模式, 每秒钟都会给你返回信息, 直到任务完成,感觉比网易方便一些四个步骤: 准备, 上传, 获取状态, 获取结果
需要mp3转wav需要不需要

建议用哪个

都可以, 都差不多

写代码利器

本文的代码大部分是 ChatGPT4 写的,

你如果不想动手, 可以像我一样, 直接复制黏贴网易有道云的文档, 然后叫chatgpt给你代码

就像这样

我用的 ChatGPT4, 是这个

ChatGPT联网版, Stable Diffusion画图, 这个星球全都有, 低调使用, 别外传

声明

本文仅供学习研究使用, 禁止用于其他用途


微信公众号 牙叔教程