构建语音日记应用:Whisper API与Node.js集成指南
概述
语音日记(Voice Journal)是通过语音输入记录日常想法和感受的健康应用。相比文字日记,语音更自然、更能捕捉情感。
使用OpenAI的Whisper API,我们可以实现高精度的语音转文字功能。
系统架构
正在渲染图表...
flowchart LR
A[React Native<br/>移动端] -->|录音| B[Node.js<br/>后端]
A -->|API调用| B
B --> C[Whisper API]
B --> D[(PostgreSQL<br/>文本存储)]
style A fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
style B fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
style C fill:#fff3e0,stroke:#f57c00,stroke-width:2px
style D fill:#e8f5e9,stroke:#388e3c,stroke-width:2px数据流程说明:
- 录音阶段:React Native 移动端录制音频
- API调用:将音频上传到 Node.js 后端
- 语音转文字:后端调用 Whisper API 进行转录
- 数据存储:将转录文本存储到 PostgreSQL 数据库
前端录音实现
React Native录音组件
code
// VoiceRecorder.tsx
import React, { useState, useEffect } from 'react';
import { Audio } from 'expo-av';
import * as FileSystem from 'expo-file-system';
export function VoiceRecorder() {
const [recording, setRecording] = useState(false);
const [audioUri, setAudioUri] = useState<string | null>(null);
const [recordingInstance, setRecordingInstance] = useState<Audio.Recording>();
async function startRecording() {
try {
console.log('Requesting permissions..');
await Audio.requestPermissionsAsync();
// 设置录音配置
const recording = new Audio.Recording();
setRecordingInstance(recording);
await recording.prepareToRecordAsync({
android: {
extension: '.m4a',
outputFormat: Audio.AndroidAudioOutputFormat.MPEG_4,
audioEncoder: Audio.AndroidAudioEncoder.AAC,
},
ios: {
extension: '.m4a',
outputFormat: Audio.IOSAudioFormat.MPEG_4AAC,
audioQuality: Audio.IOSAudioQuality.HIGH,
},
web: {
mimeType: 'audio/webm',
},
});
await recording.startAsync();
setRecording(true);
} catch (err) {
console.error('Failed to start recording', err);
}
}
async function stopRecording() {
if (!recordingInstance) return;
console.log('Stopping recording..');
setRecording(false);
await recordingInstance.stopAndUnloadAsync();
const uri = recordingInstance.getURI();
setAudioUri(uri);
// 上传到服务器
await uploadAudio(uri);
}
async function uploadAudio(uri: string) {
const formData = new FormData();
formData.append('audio', {
uri,
name: 'voice.m4a',
type: 'audio/m4a',
} as any);
try {
const response = await fetch('http://localhost:3000/api/transcribe', {
method: 'POST',
body: formData,
});
const result = await response.json();
console.log('Transcription:', result.text);
} catch (error) {
console.error('Upload failed:', error);
}
}
return (
<View>
<Button
title={recording ? '停止录音' : '开始录音'}
onPress={recording ? stopRecording : startRecording}
/>
{audioUri && <Text>录音已保存</Text>}
</View>
);
}
Code collapsed
后端API实现
Express服务器 + Whisper集成
code
// server.js
const express = require('express');
const multer = require('multer');
const FormData = require('form-data');
const fs = require('fs');
const { Configuration, OpenAIApi } = require('openai');
const app = express();
const upload = multer({ dest: 'uploads/' });
// OpenAI配置
const configuration = new Configuration({
apiKey: process.env.OPENAI_API_KEY,
});
const openai = new OpenAIApi(configuration);
// 音频转录端点
app.post('/api/transcribe', upload.single('audio'), async (req, res) => {
try {
const audioFile = req.file;
// 读取音频文件
const audioData = fs.createReadStream(audioFile.path);
// 调用Whisper API
const response = await openai.createTranscription({
file: audioData,
model: 'whisper-1',
language: 'zh', // 中文
response_format: 'text',
});
const transcribedText = response.data.text;
// 保存到数据库
await saveJournalEntry({
text: transcribedText,
audioPath: audioFile.filename,
timestamp: new Date(),
});
res.json({
success: true,
text: transcribedText,
});
// 删除临时文件
fs.unlinkSync(audioFile.path);
} catch (error) {
console.error('Transcription error:', error);
res.status(500).json({ error: 'Transcription failed' });
}
});
// 获取日记列表
app.get('/api/journals', async (req, res) => {
try {
const entries = await JournalEntry.findAll({
order: [['timestamp', 'DESC']],
limit: 20,
});
res.json(entries);
} catch (error) {
res.status(500).json({ error: 'Failed to fetch journals' });
}
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
Code collapsed
数据库模型
Sequelize模型定义
code
// models/JournalEntry.js
const { DataTypes } = require('sequelize');
module.exports = (sequelize) => {
const JournalEntry = sequelize.define('JournalEntry', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
},
text: {
type: DataTypes.TEXT,
allowNull: false,
},
audioPath: {
type: DataTypes.STRING,
allowNull: true,
},
timestamp: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW,
},
mood: {
type: DataTypes.STRING,
allowNull: true,
},
tags: {
type: DataTypes.JSON,
allowNull: true,
},
createdAt: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW,
},
updatedAt: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW,
},
});
return JournalEntry;
};
Code collapsed
关键功能
1. 情感分析
code
// 分析日记的情感倾向
async function analyzeSentiment(text) {
// 可以使用情感分析API或本地模型
const response = await openai.createCompletion({
model: 'gpt-4',
messages: [
{
role: 'system',
content: '分析以下日记的情感倾向(积极/中性/消极)并提取关键词:',
},
{
role: 'user',
content: text,
},
],
});
return response.choices[0].message.content;
}
Code collapsed
2. 摘要生成
code
// 生成日记摘要
async function generateSummary(entries) {
const allText = entries.map(e => e.text).join('\n');
const response = await openai.createCompletion({
model: 'gpt-4',
messages: [
{
role: 'system',
content: '为以下日记条目生成简短摘要(100字以内):',
},
{
role: 'user',
content: allText,
},
],
});
return response.choices[0].message.content;
}
Code collapsed
关键要点
- Whisper API支持多种语言:包括中文
- 音频文件需合理压缩:平衡质量和传输速度
- 后端处理转录文本:情感分析、摘要生成等
- 安全存储音频和文本:隐私保护
- 提供文字编辑功能:修正识别错误
常见问题
Whisper API价格如何?
- 按分钟计费:$0.006/分钟
- 100小时约$36:适合个人项目
如何提高识别准确率?
- 使用高质量麦克风
- 环境安静时录音
- 说话清晰、靠近麦克风
- 后处理文本修正
支持哪些音频格式?
MP3、M4A、WAV、OGG等主流格式
参考资料
- OpenAI Whisper API文档
- React Native Audio API
- Express文件上传处理
发布日期:2026年3月8日 最后更新:2026年3月8日