我正在解决一个过去几天一直困扰我的问题。我使用Node.js with Express(v4.18.2)最终创建一个Firebase部署,可以接收视频URL并将音频mp3输出到Firebase Firestore。我已经取得了一些进展,但在某些方面仍然不成功。
我不能使用fs在本地保存文件,但在这个例子中,我已经证明了它可以使用FS。我成功地保存了一个本地.mp3文件。
首先,我有几个功能:
async function downloadVideo(videoUrl) {
try {
const response = await axios.get(videoUrl, {
responseType: 'stream',
});
if (response.status === 200) {
return response.data;
} else {
throw new Error('Failed to fetch the video');
}
} catch (error) {
throw new Error('Error fetching the video: ' + error.message);
}
}
async function extractAudioFromVideo(videoUrl) {
try {
const videoStream = await downloadVideo(videoUrl);
// Create a PassThrough stream to pipe the video data
const passThrough = new PassThrough();
videoStream.pipe(passThrough);
const outputFile = 'output.mp3';
const outputStream = fs.createWriteStream(outputFile);
return new Promise((resolve, reject) => {
const audioBuffers = [];
passThrough.on('data', chunk => {
audioBuffers.push(chunk)
outputStream.write(chunk); // Write chunks to a local file
});
passThrough.on('error', err => {
reject(err);
});
ffmpeg()
.input(passThrough)
.output('/dev/null') // Null output as a placeholder
.outputOptions('-vn') // Extract audio only
.noVideo()
.audioQuality(0)
.audioCodec('libmp3lame') // Set audio codec
.format('mp3')
.on('end', () => {
const audioBuffer = Buffer.concat(audioBuffers)
if (audioBuffer.length > 0) {
resolve(audioBuffer);
} else {
reject(new Error('Empty audio buffer'));
}
})
.on('error', err => reject(err))
.run();
})
} catch (error) {
throw new Error('Error extracting audio: ' + error.message);
}
}
async function saveAudioToFirebase(audioBuffer, fileName) {
try {
let storage = admin.storage()
let storageRef = storage.bucket(serviceAccount.storage_bucket_content)
const file = storageRef.file(fileName) // Specify the desired file name here
const renamedFileName = fileName.replace(/\.[^/.]+$/, '.mp3'); // Change the file extension to .mp3
await file.save(audioBuffer, {
metadata: {
contentType: 'audio/mpeg', // Adjust the content type as needed
},
});
await file.setMetadata({
contentType: 'audio/mpeg'
})
await file.move(renamedFileName); // Rename the file with the .mp3 extension
console.log('Audio saved to Firebase Storage.');
} catch (error) {
console.error('Error saving audio to Firebase Storage:', error);
}
}
什么工作:
- 通过Axios下载视频
- 保存到Firebase存储(没有初始化或指向Firebase的指针问题)
- 输出名为“output.mp3”的本地.mp3文件
- 我能够记录
extractAudioFromVideo
的结果,并在我的终端中记录缓冲区
什么不起作用:
- 将一个文件保存到Firebase Storage中,该文件是.mp3。它在URL中显示为“.mp3”,内容类型为“audio/mpeg”,但实际上它是.mp4。仍然有视频并在浏览器窗口中播放视频。
我愿意使用其他库,如tmp,如果建议和解决方案的工作。
3条答案
按热度按时间laawzig21#
字符串
4bbkushb2#
前言
不要指望StackOverflow经常给出这样的答案,我只是喜欢解决这个问题,然后就忘乎所以了。
问题
看看你目前的方法,视频文件首先下载到内存中(在音频转换之前,作为
audioBuffer
),然后写出来作为output.mp3
的文件(在音频转换之前)。这是由这些行引起的(为了清晰起见,重新排列):字符串
请注意,上面的行没有提到MP3文件转换。这就是为什么您上传的文件和本地文件都是具有
.mp3
文件扩展名的视频。在这些行下面,您将passThrough
流的输出馈送到ffmpeg
并丢弃结果(通过将其发送到/dev/null
)。型
完全不需要与文件系统交互,应该可以提取原始视频流,通过删除视频内容并根据需要转换音轨来转换流内容,然后将生成的音频流直接加载到Google Cloud Storage中。这被称为ETL管道(用于提取、转换、加载),并有助于最大限度地减少托管此云功能所需的资源。
潜在解决方案
在第一个代码块中,我已经将
extractAudioFromVideo
和saveAudioToFirebase
辅助方法合并到一个streamAudioTrackToCloudStorage
辅助方法中。将这些组件放在一起有助于防止传递没有适当侦听器的流。它还有助于将当前上下文绑定到转换和加载步骤中抛出的错误。这一点特别重要,因为正在上传的文件可能会被如果FFMPEG不能正确处理传入的流,则为不完整或空。由错误处理代码来处理不完整的文件。streamAudioTrackToCloudStorage
方法接受一个downloadable
布尔参数,该参数可以在上传时生成Firebase Storage下载URL。这对于将文件的记录插入Cloud Firestore或实时数据库(如果是公共消费)非常有用。型
现在我们已经有了transform和load流,我们需要获得extract流。
随着Node v18中原生Node Fetch API的引入,我已经删除了
axios
,因为它实际上并没有被用于本步骤的任何有用功能。如果您正在使用拦截器进行身份验证或类似的操作,您可以修改下面的脚本以将其重新添加进去。在这个代码块中,我们定义了
storeAudioFromRemoteVideo
辅助方法,它接受要转换的视频URL沿着转换后的mp3文件的最终上传路径。除非提供另一个GCS bucket作为选项参数的一部分,该文件将被上传到您在共享的代码中指定的默认存储桶。选项参数的其余属性作为第二个参数,如果你需要指定像Authorization
头、API键或请求体之类的东西。型
用法
现在定义了上述方法,您的Cloud Function代码可能如下所示:
型
可能的后续步骤:
jobDurationMS
值和执行此函数的项目成本对性能进行基准测试。kg7wmglp3#
对于那些正在寻找这个问题的解决方案的人来说,我相信仍然有一种方法可以通过passthrough来解决这个问题,但是我将使用'os' Node.js包来提供我的解决方案。
字符串