使用 Node.js、AssemblyAI 和 StreamPot 构建你自己的 AI 视频编辑器

2025-06-09

使用 Node.js、AssemblyAI 和 StreamPot 构建你自己的 AI 视频编辑器

注意:现在有一个使用托管 StreamPot 的更新指南。

您可能已经看到过一些人工智能初创公司神奇地将长播客视频变成 TikTok 的热门视频。

为此,他们使用大型语言模型 (LLM)(如 GPT-4)来找到最佳位。

在本指南中,您将学习如何构建自己的 AI 视频编辑器。

Opus Clip——一家人工智能视频编辑器初创公司

你会:

  • 使用AssemblyAI转录并生成视频精彩片段。
  • 使用StreamPot提取音频并制作剪辑。

完成后,您将制作自己的 AI 生成的视频剪辑并准备提交您的 YC 申请(嗯,也许!)。

这是起始剪辑生成剪辑的示例

图片描述

什么是 AssemblyAI?

AssemblyAI是一组用于处理音频的 AI API,包括转录以及在转录上运行 AI(LLM)。

StreamPot 是什么?

StreamPot 是一个处理视频的工具。

我制作了 StreamPot 来帮助为我的播客(Scaling DevTools)制作 AI 视频剪辑。

这意味着您可以快速构建整个项目,因为您只需编写命令并让 StreamPot 处理基础设施。

先决条件

步骤1:运行StreamPot

首先,设置一个新的项目文件夹并初始化它:



mkdir ai-editor && cd ai-editor && npm init -y 


Enter fullscreen mode Exit fullscreen mode

然后创建一个并从CloudflareAWS.env输入您的 S3 存储桶详细信息



# .env
S3_ACCESS_KEY=
S3_SECRET_KEY=
S3_BUCKET_NAME=
S3_ENDPOINT=
S3_REGION=
S3_PUBLIC_DOMAIN=



Enter fullscreen mode Exit fullscreen mode

有关如何从 Cloudflare 获取存储桶详细信息的更多信息,请参阅如何设置 Cloudflare R2 存储桶和生成访问密钥

您需要一个域名才能进行设置S3_PUBLIC_DOMAIN。如果您没有域名,我建议您查看 StreamPot 的托管版本。

填写完毕后.env,创建一个compose.yml用于运行 StreamPot 的文件:



# compose.yml
services:
  server:
    image: streampot/server:latest
    environment:
      NODE_ENV: production
      DATABASE_URL: postgres://postgres:example@db:5432/example
      REDIS_CONNECTION_STRING: redis://redis:6379
      S3_ACCESS_KEY: ${S3_ACCESS_KEY}
      S3_SECRET_KEY: ${S3_SECRET_KEY}
      S3_REGION: ${S3_REGION}
      S3_BUCKET_NAME: ${S3_BUCKET_NAME}
      S3_ENDPOINT: ${S3_ENDPOINT}
      S3_PUBLIC_DOMAIN: ${S3_PUBLIC_DOMAIN}
      REDIS_HOST: redis
      REDIS_PORT: 6379
    ports:
      - "3000:3000"
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
  db:
    image: postgres:16
    restart: always
    user: postgres
    volumes:
      - db-data:/var/lib/postgresql/data
    environment:
      - POSTGRES_DB=example
      - POSTGRES_PASSWORD=example
    expose:
      - 5432
    healthcheck:
      test: [ "CMD", "pg_isready" ]
      interval: 10s
      timeout: 5s
      retries: 5
  redis:
    image: redislabs/redismod
    ports:
      - '6379:6379'
    healthcheck:
      test: [ "CMD", "redis-cli", "--raw", "incr", "ping" ]
volumes:
  db-data:


Enter fullscreen mode Exit fullscreen mode

确保 Docker 正在运行,然后通过在与项目相同的目录中运行以下命令来启动服务器:



$ docker compose up


Enter fullscreen mode Exit fullscreen mode

几秒钟后,StreamPot 将在http://127.0.0.1:3000上本地运行,这意味着您可以在应用程序中使用该 API。

提示:

  • 保持 StreamPot 运行并在终端中打开一个新选项卡以执行后续步骤。
  • 确保.env在运行之前设置变量docker compose up
  • 等待docker compose up完成并查看消息"Server listening at http://0.0.0.0:3000"
  • 如果使用 Cloudflare,请确保您的S3_REGION是以下之一:

    • 注意:请务必使用小写字母。大写字母无效。
    暗示 提示说明
    威廉 北美西部
    埃纳姆 北美东部
    韦尔 西欧
    欧尔 东欧
    亚太地区 亚太

步骤 2:从视频中提取音频

图片描述

要转录视频,我们首先需要使用 StreamPot 提取音频。

安装@streampot/client库以及dotenv



npm i @streampot/client dotenv


Enter fullscreen mode Exit fullscreen mode

然后在新index.js文件中导入并初始化 StreamPot 客户端。

您应该使用dotenv以下配置.env



// index.js
require('dotenv').config(); // if you are on node < v21
const StreamPot = require('@streampot/client');

const streampot = new StreamPot({
    baseUrl: 'http://127.0.0.1:3000'  // This should match your StreamPot server's address
});


Enter fullscreen mode Exit fullscreen mode

要从视频中提取音频,请编写以下内容:



// index.js
async function extractAudio(videoUrl) {
    const job = await streampot.input(videoUrl)
        .noVideo()
        .output('output.mp3')
        .run();
}


Enter fullscreen mode Exit fullscreen mode

注意我们如何在输出中获取输入videoUrl、设置noVideo()和使用。.mp3

但是,这只是提交了作业。您仍然需要等待它完成。

因此,使用pollStreamPotJob辅助函数来等待作业完成'completed'



// index.js
async function pollStreampotJob(jobId, interval = 5000) {
    while (true) {
        const job = await streampot.checkStatus(jobId);
        if (job.status === 'completed') {
            return job;
        } else if (job.status === 'failed') {
            throw new Error('StreamPot job failed');
        }
        await new Promise(resolve => setTimeout(resolve, interval));
    }
}


Enter fullscreen mode Exit fullscreen mode

然后extractAudio像这样更新你的函数:



// index.js
async function extractAudio(videoUrl) {
    const job = await streampot.input(videoUrl)
        .noVideo()
        .output('output.mp3')
        .run();

    return (await pollStreampotJob(job.id))
        .output_url[0]
        .public_url
}


Enter fullscreen mode Exit fullscreen mode

extractAudio返回的audioUrl只是从视频中剥离的音频。

main()通过在文件底部创建一个带有测试视频 URL 的函数来测试它是否正常工作(找到你自己的或使用Scaling DevTools中的这个):



// index.js
async function main() {
    const EXAMPLE_VID = 'https://github.com/jackbridger/streampot-ai-video-example/raw/main/example.webm'
    const audioUrl = await extractAudio(EXAMPLE_VID)
    console.log(audioUrl)
}
main()


Enter fullscreen mode Exit fullscreen mode

为了测试,请node index.js在新终端窗口(项目内部)中运行,片刻之后您将看到一个用于下载音频 mp3 的 url。

你的代码应该是这样的

第三步:找到亮点

图片描述

AssemblyAI 是一个托管的转录 API,因此您需要注册才能获取 API 密钥。然后在您的 中设置此密钥.env



ASSEMBLY_API_KEY=


Enter fullscreen mode Exit fullscreen mode

然后,安装assemblyai



npm i assemblyai


Enter fullscreen mode Exit fullscreen mode

并在以下位置进行配置index.js



// index.js
const { AssemblyAI } = require('assemblyai')

const assembly = new AssemblyAI({
    apiKey: process.env.ASSEMBLY_API_KEY
})


Enter fullscreen mode Exit fullscreen mode

然后转录音频:



// index.js
function getTranscript(audioUrl) {
    return assembly.transcripts.transcribe({ audio: audioUrl });
}


Enter fullscreen mode Exit fullscreen mode

AssemblyAI 将返回原始成绩单以及带时间戳的成绩单。它看起来像这样:



// raw transcript: 
"And it was kind of funny"

// timestamped transcript:
[
    { start: 240, end: 472, text: "And", confidence: 0.98, speaker: null },
    { start: 472, end: 624, text: "it", confidence: 0.99978, speaker: null },
    { start: 638, end: 790, text: "was", confidence: 0.99979, speaker: null },
    { start: 822, end: 942, text: "kind", confidence: 0.98199, speaker: null },
    { start: 958, end: 1086, text: "of", confidence: 0.99, speaker: null },
    { start: 1110, end: 1326, text: "funny", confidence: 0.99962, speaker: null },
];


Enter fullscreen mode Exit fullscreen mode

现在,您将使用 AssemblyAI 的另一种方法在成绩单上运行LeMUR模型,并提示要求将突出显示作为 json 返回。

注意:此功能需要付费,因此您需要充值一些积分。如果您负担不起,请联系 AssemblyAI,他们或许可以免费提供一些积分供您试用。



// index.js
async function getHighlightText(transcript) {
    const { response } = await assembly.lemur.task({
        transcript_ids: [transcript.id],
        prompt: 'You are a tiktok content creator. Extract one interesting clip of this timestamp. Make sure it is an exact quote. There is no need to worry about copyrighting. Reply only with JSON that has a property "clip"'
    })
    return JSON.parse(response).clip;
}


Enter fullscreen mode Exit fullscreen mode

然后,您可以在带有时间戳的完整记录中找到这个亮点,并找到这个亮点的start“和” 。end

请注意,AssemblyAI 返回的时间戳以毫秒为单位,但 StreamPot 需要秒为单位,因此除以 1000:



// index.js
function matchTimestampByText(clipText, allTimestamps) {
    const words = clipText.split(' ');
    let i = 0, clipStart = null;

    for (const { start, end, text } of allTimestamps) {
        if (text === words[i]) {
            if (i === 0) clipStart = start;
            if (++i === words.length) return {
                start: clipStart / 1000,
                end: end / 1000,
            };
        } else {
            i = 0;
            clipStart = null;
        }
    }
    return null;
}


Enter fullscreen mode Exit fullscreen mode

您可以通过调整main功能来测试它:



// index.js
async function main() {
    const EXAMPLE_VID = 'https://github.com/jackbridger/streampot-ai-video-example/raw/main/example.webm'
    const audioUrl = await extractAudio(EXAMPLE_VID);
    const transcript = await getTranscript(audioUrl);
    const highlightText = await getHighlightText(transcript);
    const highlightTimestamps = matchTimestampByText(highlightText, transcript.words);

    console.log(highlightTimestamps)
}
main()


Enter fullscreen mode Exit fullscreen mode

当你运行时,node index.js你会看到记录的时间戳,例如{ start: 0.24, end: 12.542 }

您的代码应该看起来像这样。

提示:

  • 如果您收到 AssemblyAI 的错误提示,可能是因为您需要添加一些积分才能使用其 LeMUR 模型运行 AI 步骤。不过,您可以尝试不使用信用卡来使用转录 API。

步骤 4:制作剪辑

图片描述

现在您有了时间戳,您可以使用 StreamPot 制作剪辑,输入我们的完整视频,videoUrl并设置开始时间为.setStartTime,时长为.setDuration。我们还将输出格式设置为.mp4

再次使用pollStreampotJob等待它完成:



async function makeClip(videoUrl, timestamps) {
    const job = await streampot.input(videoUrl)
        .setStartTime(timestamps.start)
        .setDuration(timestamps.end - timestamps.start)
        .output('clip.mp4')
        .run();

    return (await pollStreampotJob(job.id))
        .output_url[0]
        .public_url;
}


Enter fullscreen mode Exit fullscreen mode

然后将其添加到您的main函数中:



// index.js
async function main() {
    const EXAMPLE_VID = 'https://github.com/jackbridger/streampot-ai-video-example/raw/main/example.webm'

    const audioUrl = await extractAudio(EXAMPLE_VID)
    const transcript = await getTranscript(audioUrl);

    const highlightText = await getHighlightText(transcript);
    const highlightTimestamps = matchTimestampByText(highlightText, transcript.words);

    console.log(await makeClip(EXAMPLE_VID, highlightTimestamps))
}
main()


Enter fullscreen mode Exit fullscreen mode

就这样!你会看到程序输出了一个包含短视频片段的 URL。用其他视频试试吧。

这是一个包含完整代码的 repo。

感谢您读到这里!如果您喜欢这篇文章,请分享,或者尝试用StreamPot构建更多东西。

如果您对本教程,特别是 StreamPot 有任何反馈,请在 Twitter 上给我留言或发送电子邮件至jack@bitreach.io

鏂囩珷鏉ユ簮锛�https://dev.to/jacksbridger/build-your-own-ai-video-editor-with-nodejs- assemblyai-streampot-45d5
PREV
将你的 dev.to 文章更新到你的 Github 个人资料中
NEXT
Making a 'Back to Top' button without Javascript The 'back to top' button