FFmpeg 处理、Firebase 函数和 Firebase 存储上传的异步行为和性能问题

问题描述 投票:0回答:1

我实现了一项在将视频文件上传到 Firebase Storage 时激活的功能。该功能利用 Fluent FFmpeg 将视频分割成片段,然后将这些片段保存回存储中。在测试过程中,我上传了一个500MB的视频,大约3分钟长,并将其分割成36个片段,每个片段5秒,从而产生36个文件。该过程最初看起来很高效:该函数本身在大约 30 秒内完成。然而,我注意到 FFmpeg 处理继续独立进行,额外花费了 18 分钟。随后,将片段上传回存储还需要 59 分钟。

我在具有 2GB 内存和 60 秒超时设置的第一代 Firebase 函数上运行此程序。基于这个设置,我有几个问题:

  1. 当 FFmpeg 任务继续并行、异步运行时,Firebase 函数成功完成是典型的情况吗?
  2. 什么可能导致将分段文件保存回 Firebase 存储时出现严重延迟?是否有任何策略可以提高此性能?

我很欣赏有关如何解决这些问题的见解或建议,特别是优化存储节省步骤的方法。

这是我的代码

exports.segmentVideo = functions.storage.object().onFinalize(
async (object) => {
  const filePath = object.name;

  if (filePath.startsWith("test/")) {
    console.log("1. Segmentation STARTED");
    const fileName = filePath.split("/").pop();
    const bucketName = object.bucket;
    const tempLocalFile = path.join(os.tmpdir(), fileName);
    const tempSegmentFolder = path.join(os.tmpdir(), "mySegments");

    try {
      await fs.mkdir(tempSegmentFolder, {recursive: true});
    } catch (err) {
      console.error("Error creating temp folder:", err);
      return;
    }

    await storage.bucket(bucketName)
        .file(filePath)
        .download({destination: tempLocalFile});

    const metadata = object.metadata;
    const segmentDuration = metadata.segmentDuration ?
        parseInt(metadata.segmentDuration, 10) :
        30;
    const userEmail = metadata.userEmail;
    console.log("2. Processing video with FFmpeg");

    ffmpeg(tempLocalFile)
        .outputOptions([
          `-f segment`,
          `-segment_time ${segmentDuration}`,
          `-reset_timestamps 1`,
          `-c copy`,
          //              `-c:v libx264`, // Re-encode video with H.264
          //              `-c:a aac`, // Re-encode audio with AAC
        ])
        .output(path.join(tempSegmentFolder,
            `output_${fileName}_%03d.mp4`))
        .on("start", (commandLine) => {
          console.log(`FFmpeg started with command: ${commandLine}`);
        })
        .on("progress", (progress) => {
          console.log(`Processing: ${progress.percent}% done`);
        })
        .on("error", (err, stdout, stderr) => {
          console.error(`Error occurred: ${err.message}`);
          console.log(`FFmpeg stdout:\n${stdout}`);
          console.log(`FFmpeg stderr:\n${stderr}`);
        })


        .on("end", async () => {
          console.log("Segmentation finished");

          const files = await fs.readdir(tempSegmentFolder);
          const segmentedFiles = files.filter((file) =>
            file.startsWith(`output_${fileName}`) &&
        file.endsWith(".mp4"));

          const segmentUrls = []; // Array to hold URLs of uploaded segments

          for (const file of segmentedFiles) {
            const destination = `segments/${file}`;
            await storage.bucket(bucketName)
                .upload(path.join(tempSegmentFolder, file), {destination});

            // Using Firebase Admin SDK to get the download URL
            const fileUrl = `https://firebasestorage.googleapis.com/v0/b/${bucketName}/o/${encodeURIComponent(destination)}?alt=media`;
            segmentUrls.push(fileUrl);
            console.log(`Link ${file}:${fileUrl}`);

            console.log(`Uploaded ${file} to ${destination}`);
          }

          // Sending an email with the video links
          // Ensure userEmail is fetched from metadata
          // and sendEmail function is defined
          if (userEmail && segmentUrls.length > 0) {
            await sendEmail(userEmail, segmentUrls)
                .then(() => console
                    .log("Email sent successfully."))
                .catch((error) => console
                    .error("Failed to send email:", error));
          } else {
            console.log("Email not sent: User email is missing" +
            "\nor no segments to send.");
          }


          await Promise.all(segmentedFiles.map((file) =>
            fs.unlink(path.join(tempSegmentFolder, file))));
          await fs.rm(tempSegmentFolder, {recursive: true, force: true});
        })
        .run();
  } else {
    console.log("File not in target folder");
  }
});
firebase google-cloud-functions firebase-storage
1个回答
0
投票

尝试下面的操作,我添加了突出显示的更改,并添加了以

//<--
开头的评论。

exports.segmentVideo = functions.storage.object().onFinalize(
  async (object) => {
    const filePath = object.name;

    if (filePath.startsWith("test/")) {
      console.log("1. Segmentation STARTED");
      const fileName = filePath.split("/").pop();
      const bucketName = object.bucket;
      const tempLocalFile = path.join(os.tmpdir(), fileName);
      const tempSegmentFolder = path.join(os.tmpdir(), "mySegments");

      try {
        await fs.mkdir(tempSegmentFolder, { recursive: true });
      } catch (err) {
        console.error("Error creating temp folder:", err);
        return;
      }

      await storage.bucket(bucketName)
        .file(filePath)
        .download({ destination: tempLocalFile });

      const metadata = object.metadata;
      const segmentDuration = metadata.segmentDuration
        ? parseInt(metadata.segmentDuration, 10)
        : 30;
      const userEmail = metadata.userEmail;
      console.log("2. Processing video with FFmpeg");

      const ffmpegPromise = new Promise((resolve, reject) => {
        ffmpeg(tempLocalFile)
          .outputOptions([
            `-f segment`,
            `-segment_time ${segmentDuration}`,
            `-reset_timestamps 1`,
            `-c copy`,
          ])
          .output(path.join(tempSegmentFolder, `output_${fileName}_%03d.mp4`))
          .on("start", (commandLine) => {
            console.log(`FFmpeg started with command: ${commandLine}`);
          })
          .on("progress", (progress) => {
            console.log(`Processing: ${progress.percent}% done`);
          })
          .on("error", (err, stdout, stderr) => {
            console.error(`Error occurred: ${err.message}`);
            console.log(`FFmpeg stdout:\n${stdout}`);
            console.log(`FFmpeg stderr:\n${stderr}`);
            reject(err);
          })
          .on("end", () => {
            console.log("Segmentation finished");
            resolve();
          })
          .run();
      });

      const uploadPromises = [];

      ffmpegPromise.then(async () => {
        const files = await fs.readdir(tempSegmentFolder);
        const segmentedFiles = files.filter(
          (file) =>
            file.startsWith(`output_${fileName}`) && file.endsWith(".mp4")
        );

        const segmentUrls = []; // Array to hold URLs of uploaded segments

        for (const file of segmentedFiles) {
          const destination = `segments/${file}`;
          const uploadPromise = storage
            .bucket(bucketName)
            .upload(path.join(tempSegmentFolder, file), { destination })
            .then(() => {                 //<-- use then to ensure the upload occer and then move to next
              // Using Firebase Admin SDK to get the download URL
              const fileUrl = `https://firebasestorage.googleapis.com/v0/b/${bucketName}/o/${encodeURIComponent(
                destination
              )}?alt=media`;
              segmentUrls.push(fileUrl);
              console.log(`Link ${file}:${fileUrl}`);
              console.log(`Uploaded ${file} to ${destination}`);
            })
            .catch((error) => {  //<-- catch if any 
              console.error(`error occered ... at catch while uploading then file: ${fileName}:`, error);
            });

          uploadPromises.push(uploadPromise);
        }

      
        await Promise.all(uploadPromises);// <--await to complet uploadPromises 

        // Sending an email with the video links
        // Ensure userEmail is fetched from metadata
        // and sendEmail function is defined
        if (userEmail && segmentUrls.length > 0) {
          await sendEmail(userEmail, segmentUrls)
            .then(() => console.log("Email sent successfully."))
            .catch((error) => console.error("Failed to send email:", error));
        } else {
          console.log(
            "Email not sent: User email is missing or no segments to send."
          );
        }


        await Promise.all(
          segmentedFiles.map((file) =>
            fs.unlink(path.join(tempSegmentFolder, file))
          )
        );
        await fs.rm(tempSegmentFolder, { recursive: true, force: true });
      });

      return ffmpegPromise;
    } else {
      console.log("File not in target folder");
    }
  }
);
© www.soinside.com 2019 - 2024. All rights reserved.