You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Node.js中使用ytdl-core的chooseFormat无法下载纯视频文件

问题:ytdl-core选择格式下载纯视频时生成0字节文件

我使用Node.js和ytdl-core包下载YouTube视频,基础下载代码可正常运行:

ytdl('http://www.youtube.com/watch?v=aqz-KE-bpKQ')
  .pipe(fs.createWriteStream('video.mp4'));

但要获取高清视频需单独下载音视频文件再用ffmpeg合并。目前通过自定义函数调用「选择视频格式」功能下载纯视频时遇到问题:

我设置了/download端点,调用controller中的download()函数,该函数会依次执行getVideoInfo()和downloadVideo()。其中getVideoInfo()会在/public目录生成getInfo.json和videoFormat.json文件,downloadVideo()需将视频文件下载到/public目录。

JSON文件能成功生成,但下载的视频文件大小为0字节。相关代码如下:

const fs = require("fs")
const path = require("path")
const ytdl = require("ytdl-core")
const cmd = require("node-cmd")
const axios = require("axios")

async function download(req, res) {
  const url = "https://www.youtube.com/watch?v=Qdi_yp9FA-I"
  const videoPath = "public/video_only.mp4"

  try {
    const videoFormat = await getVideoInfo(url)
    await downloadVideo(url, videoFormat, videoPath)

    console.log('Video is downloaded.')
  } catch (error) {
    console.error("Error in download function:", error);
    res.status(500).send("Internal Server Error");
  }
}

async function getVideoInfo(url) {
  const info = await ytdl.getInfo(url)
  console.log("Title: " + info.videoDetails.title)

  // Save info to a JSON file
  fs.writeFileSync("public/getInfo.json", JSON.stringify(info, null, 2))

  const videoFormat = ytdl.chooseFormat(info.formats, { quality: "137" })

  // Save videoFormat to a JSON file
  fs.writeFileSync("public/videoFormat.json", JSON.stringify(videoFormat, null, 2))

  return videoFormat
}

async function downloadVideo(url, videoFormat, videoPath) {
  try {
      if (!videoFormat) {
        reject(new Error("No suitable format found."))
        return
      }

      ytdl(url, { format: videoFormat }).pipe(fs.createWriteStream(videoPath))
  } catch (error) {
    throw new Error("Error downloading video: " + error.message)
  }
}

module.exports = {
  download,
}

问题原因

  1. downloadVideo是async函数,但内部的ytdl.pipe()属于流式异步操作,未被封装为Promise,导致await downloadVideo()不会等待下载完成就执行后续代码,文件流还未写入内容就被终止,生成0字节文件。
  2. downloadVideo中直接调用reject()但未处于Promise上下文,会触发未捕获的错误。

解决方案

将downloadVideo改造为返回Promise的函数,通过监听流的finish和error事件控制Promise状态,确保await能正确等待下载完成:

const fs = require("fs")
const path = require("path")
const ytdl = require("ytdl-core")
const cmd = require("node-cmd")
const axios = require("axios")

async function download(req, res) {
  const url = "https://www.youtube.com/watch?v=Qdi_yp9FA-I"
  const videoPath = "public/video_only.mp4"

  try {
    const videoFormat = await getVideoInfo(url)
    await downloadVideo(url, videoFormat, videoPath)

    console.log('Video is downloaded.')
    // 下载完成后给客户端返回响应
    res.status(200).send("Video downloaded successfully")
  } catch (error) {
    console.error("Error in download function:", error);
    res.status(500).send("Internal Server Error");
  }
}

async function getVideoInfo(url) {
  const info = await ytdl.getInfo(url)
  console.log("Title: " + info.videoDetails.title)

  // 确保public目录存在,避免写入失败
  if (!fs.existsSync("public")) {
    fs.mkdirSync("public")
  }

  fs.writeFileSync("public/getInfo.json", JSON.stringify(info, null, 2))

  const videoFormat = ytdl.chooseFormat(info.formats, { quality: "137" })

  fs.writeFileSync("public/videoFormat.json", JSON.stringify(videoFormat, null, 2))

  return videoFormat
}

// 改造为返回Promise的函数
function downloadVideo(url, videoFormat, videoPath) {
  return new Promise((resolve, reject) => {
    if (!videoFormat) {
      reject(new Error("No suitable format found."))
      return
    }

    const videoStream = ytdl(url, { format: videoFormat })
    const writeStream = fs.createWriteStream(videoPath)

    videoStream.pipe(writeStream)

    // 监听写入完成事件
    writeStream.on("finish", () => {
      resolve()
    })

    // 监听流错误事件
    videoStream.on("error", (err) => {
      reject(new Error("Error downloading video: " + err.message))
    })

    writeStream.on("error", (err) => {
      reject(new Error("Error writing video file: " + err.message))
    })
  })
}

module.exports = {
  download,
}

额外优化点

  • 增加public目录存在性检查,避免因目录不存在导致文件写入失败。
  • 在download函数中添加下载完成后的客户端响应,让前端感知下载状态。
  • 分别监听视频流和写入流的错误事件,捕获更全面的异常。

内容的提问来源于stack exchange,提问作者Hamid

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 17:01:22