fix: ASR audio format conversion + share.vue deduplicate share creation
- TTS /tts/asr: convert non-WAV (AAC/MP3) to WAV before whisper transcription - share.vue: only create share record if no cached shareCode exists (prevents duplicate share records on every page load)
This commit is contained in:
@@ -46,13 +46,27 @@ export class TtsController {
|
|||||||
const ext = file.originalname ? path.extname(file.originalname) || '.aac' : '.aac'
|
const ext = file.originalname ? path.extname(file.originalname) || '.aac' : '.aac'
|
||||||
const dest = path.join(uploadDir, file.filename + ext)
|
const dest = path.join(uploadDir, file.filename + ext)
|
||||||
fs.renameSync(file.path, dest)
|
fs.renameSync(file.path, dest)
|
||||||
|
|
||||||
|
let wavPath = dest
|
||||||
|
if (ext.toLowerCase() !== '.wav') {
|
||||||
|
const potentialWav = dest.replace(/\.[^.]+$/, '.wav')
|
||||||
|
try {
|
||||||
|
execSync(`ffmpeg -y -i "${dest}" -ar 16000 -ac 1 -c:a pcm_s16le "${potentialWav}"`, {
|
||||||
|
timeout: 30000, encoding: 'utf8', stdio: 'pipe',
|
||||||
|
})
|
||||||
|
wavPath = potentialWav
|
||||||
|
} catch (e: any) {
|
||||||
|
this.logger.warn(`FFmpeg conversion failed: ${e.message}, using original format`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (process.env.OPENAI_API_KEY) {
|
if (process.env.OPENAI_API_KEY) {
|
||||||
const result = execSync(
|
const result = execSync(
|
||||||
`curl -s -X POST https://api.openai.com/v1/audio/transcriptions \
|
`curl -s -X POST https://api.openai.com/v1/audio/transcriptions \
|
||||||
-H "Authorization: Bearer ${process.env.OPENAI_API_KEY}" \
|
-H "Authorization: Bearer ${process.env.OPENAI_API_KEY}" \
|
||||||
-H "Content-Type: multipart/form-data" \
|
-H "Content-Type: multipart/form-data" \
|
||||||
-F "file=@${dest}" \
|
-F "file=@${wavPath}" \
|
||||||
-F "model=whisper-1" \
|
-F "model=whisper-1" \
|
||||||
-F "language=zh"`,
|
-F "language=zh"`,
|
||||||
{ encoding: 'utf8', timeout: 30000 },
|
{ encoding: 'utf8', timeout: 30000 },
|
||||||
@@ -60,7 +74,7 @@ export class TtsController {
|
|||||||
const parsed = JSON.parse(result)
|
const parsed = JSON.parse(result)
|
||||||
if (parsed.text) return { text: parsed.text.trim() }
|
if (parsed.text) return { text: parsed.text.trim() }
|
||||||
}
|
}
|
||||||
const whisperResult = execSync(`python3 -c 'import sys, whisper; model = whisper.load_model("tiny"); print(model.transcribe(sys.argv[1], language="zh")["text"].strip())' "${dest}"`, { encoding: 'utf8', timeout: 60000 })
|
const whisperResult = execSync(`python3 -c 'import sys, whisper; model = whisper.load_model("tiny"); print(model.transcribe(sys.argv[1], language="zh")["text"].strip())' "${wavPath}"`, { encoding: 'utf8', timeout: 60000 })
|
||||||
if (whisperResult && whisperResult.trim()) {
|
if (whisperResult && whisperResult.trim()) {
|
||||||
return { text: whisperResult.trim() }
|
return { text: whisperResult.trim() }
|
||||||
}
|
}
|
||||||
@@ -68,7 +82,8 @@ export class TtsController {
|
|||||||
this.logger.error(`ASR failed: ${e?.message || e}`)
|
this.logger.error(`ASR failed: ${e?.message || e}`)
|
||||||
}
|
}
|
||||||
// 清理临时文件
|
// 清理临时文件
|
||||||
try { fs.unlinkSync(dest) } catch {}
|
try { if (dest) fs.unlinkSync(dest) } catch {}
|
||||||
|
try { if (wavPath && wavPath !== dest) fs.unlinkSync(wavPath) } catch {}
|
||||||
return { text: '' }
|
return { text: '' }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -178,7 +178,8 @@ async function loadData() {
|
|||||||
if (!token) { uni.showToast({ title: '请先登录', icon: 'none' }); return }
|
if (!token) { uni.showToast({ title: '请先登录', icon: 'none' }); return }
|
||||||
const header = { Authorization: `Bearer ${token}` }
|
const header = { Authorization: `Bearer ${token}` }
|
||||||
|
|
||||||
// 先创建分享链接,缓存下来供复制使用
|
// 仅在无缓存分享链接时创建新分享记录
|
||||||
|
if (!shareUrlCached.value) {
|
||||||
try {
|
try {
|
||||||
const res = await uni.request({
|
const res = await uni.request({
|
||||||
url: api('/share/create'), method: 'POST',
|
url: api('/share/create'), method: 'POST',
|
||||||
@@ -192,6 +193,7 @@ async function loadData() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) { /* create share is best-effort */ }
|
} catch (e) { /* create share is best-effort */ }
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const [statsRes, recordsRes, visitorsRes] = await Promise.all([
|
const [statsRes, recordsRes, visitorsRes] = await Promise.all([
|
||||||
|
|||||||
Reference in New Issue
Block a user