我实现了一项在将视频文件上传到 Firebase Storage 时激活的功能。该功能利用 Fluent FFmpeg 将视频分割成片段,然后将这些片段保存回存储中。在测试过程中,我上传了一个500MB的视频,大约3分钟长,并将其分割成36个片段,每个片段5秒,从而产生36个文件。该过程最初看起来很高效:该函数本身在大约 30 秒内完成。然而,我注意到 FFmpeg 处理继续独立进行,额外花费了 18 分钟。随后,将片段上传回存储还需要 59 分钟。
我在具有 2GB 内存和 60 秒超时设置的第一代 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");
}
});
尝试下面的操作,我添加了突出显示的更改,并添加了以
//<--
开头的评论。
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");
}
}
);