Fix voiceprint upload temp file collisions

This commit is contained in:
Xulilong 2026-07-16 10:39:10 +08:00
parent 83d863daf7
commit 41baca051c
2 changed files with 83 additions and 27 deletions

View File

@ -3,9 +3,10 @@
import os
import tempfile
import mimetypes
import subprocess
from pathlib import Path
import mimetypes
import subprocess
import uuid
from pathlib import Path
from datetime import datetime
from flask import Blueprint, request, jsonify, send_file, current_app
@ -95,7 +96,42 @@ def convert_to_wav(input_path):
raise Exception(f"音频转换错误: {str(e)}")
@voiceprint_bp.route('/api/voiceprints', methods=['GET'])
def _safe_audio_extension(uploaded_file):
filename = secure_filename(uploaded_file.filename or "")
suffix = Path(filename).suffix.lower()
if suffix:
return suffix
content_type = (uploaded_file.content_type or "").lower()
if "webm" in content_type:
return ".webm"
if "wav" in content_type or "wave" in content_type:
return ".wav"
if "mpeg" in content_type or "mp3" in content_type:
return ".mp3"
if "ogg" in content_type:
return ".ogg"
if "mp4" in content_type or "m4a" in content_type:
return ".m4a"
return ".webm"
def _make_temp_audio_path(upload_dir, uploaded_file):
ext = _safe_audio_extension(uploaded_file)
filename = secure_filename(
f"temp_{datetime.now().strftime('%Y%m%d%H%M%S%f')}_{uuid.uuid4().hex}{ext}"
)
return os.path.join(upload_dir, filename)
def _make_final_voiceprint_path(upload_dir, name):
filename = secure_filename(
f"{name}_{datetime.now().strftime('%Y%m%d%H%M%S%f')}_{uuid.uuid4().hex[:8]}.wav"
)
return os.path.join(upload_dir, filename)
@voiceprint_bp.route('/api/voiceprints', methods=['GET'])
@login_required
def get_voiceprints():
"""获取声纹列表"""
@ -141,8 +177,7 @@ def create_voiceprint():
os.makedirs(voiceprint_dir, exist_ok=True)
# 保存原始音频文件到临时位置
temp_filename = secure_filename(f"temp_{datetime.now().strftime('%Y%m%d%H%M%S')}_{file.filename}")
temp_filepath = os.path.join(voiceprint_dir, temp_filename)
temp_filepath = _make_temp_audio_path(voiceprint_dir, file)
file.save(temp_filepath)
original_file_path = temp_filepath
@ -150,8 +185,7 @@ def create_voiceprint():
converted_file_path = convert_to_wav(temp_filepath)
# 保存转换后的文件
final_filename = secure_filename(f"{name}_{datetime.now().strftime('%Y%m%d%H%M%S')}.wav")
final_filepath = os.path.join(voiceprint_dir, final_filename)
final_filepath = _make_final_voiceprint_path(voiceprint_dir, name)
# 如果转换后的文件不是原始文件,则移动
if converted_file_path != temp_filepath:
@ -242,8 +276,7 @@ def update_voiceprint(voiceprint_id):
os.makedirs(voiceprint_dir, exist_ok=True)
# 保存原始音频文件到临时位置
temp_filename = secure_filename(f"temp_{datetime.now().strftime('%Y%m%d%H%M%S')}_{file.filename}")
temp_filepath = os.path.join(voiceprint_dir, temp_filename)
temp_filepath = _make_temp_audio_path(voiceprint_dir, file)
file.save(temp_filepath)
original_file_path = temp_filepath
@ -251,8 +284,7 @@ def update_voiceprint(voiceprint_id):
converted_file_path = convert_to_wav(temp_filepath)
# 保存转换后的文件
final_filename = secure_filename(f"{name}_{datetime.now().strftime('%Y%m%d%H%M%S')}.wav")
final_filepath = os.path.join(voiceprint_dir, final_filename)
final_filepath = _make_final_voiceprint_path(voiceprint_dir, name)
# 如果转换后的文件不是原始文件,则移动
if converted_file_path != temp_filepath:

View File

@ -1109,7 +1109,7 @@
style="background-color: #fff;border-radius: 4px !important;"></textarea> -->
<div class="modal-footer">
<button class="btn" onclick="closeModal()">取消</button>
<button class="btn btn-primary" onclick="saveVoice()"
<button id="saveVoiceBtn" class="btn btn-primary" onclick="saveVoice()"
style="color: #fff;">保存</button>
</div>
</div>
@ -1791,10 +1791,11 @@
let voices = []; // 存储声纹数据
let editIndex = null;
let deleteIndex = null;
let mediaRecorder;
let audioChunks = [];
let isRecording = false;
let modetypes = "add"
let mediaRecorder;
let audioChunks = [];
let isRecording = false;
let isSavingVoice = false;
let modetypes = "add"
let modechooseIndex = 0
// 打开添加/编辑弹框
async function openModal(mode, index) {
@ -1851,8 +1852,11 @@ async function synchronize() {
}
// 保存声纹信息
async function saveVoice() {
const name = document.getElementById('voiceName').value.trim();
async function saveVoice() {
if (isSavingVoice) {
return;
}
const name = document.getElementById('voiceName').value.trim();
// const desc
// = document.getElementById('voiceDesc').value.trim();
const fileInput = document.getElementById('voiceFile').files[0];
@ -1879,9 +1883,17 @@ console.log("audioBlob:", audioBlob);
// 使用 FormData 发送音频文件
const formData = new FormData();
formData.append("name", name);
formData.append("voice_data", audioBlob);
const formData = new FormData();
formData.append("name", name);
const uploadFileName = fileInput && fileInput.name ? fileInput.name : `voiceprint_${Date.now()}.webm`;
formData.append("voice_data", audioBlob, uploadFileName);
isSavingVoice = true;
const saveBtn = document.getElementById('saveVoiceBtn');
if (saveBtn) {
saveBtn.disabled = true;
saveBtn.dataset.originalText = saveBtn.textContent;
saveBtn.textContent = '保存中...';
}
if (modetypes == 'add') {
try {
@ -1901,9 +1913,15 @@ console.log("audioBlob:", audioBlob);
} catch (err) {
// console.error("上传出错:", err);
// alert("上传失败:" + err.message);
showMessage(`${err.message}`, 'error');
}
showMessage(`${err.message}`, 'error');
} finally {
isSavingVoice = false;
if (saveBtn) {
saveBtn.disabled = false;
saveBtn.textContent = saveBtn.dataset.originalText || '保存';
}
}
} else {
try {
let v = voices[modechooseIndex]
@ -1926,8 +1944,14 @@ console.log("audioBlob:", audioBlob);
} catch (err) {
// console.error("上传出错:", err);
// alert("上传失败:" + err.message);
showMessage(`${err.message}`, 'error');
}
showMessage(`${err.message}`, 'error');
} finally {
isSavingVoice = false;
if (saveBtn) {
saveBtn.disabled = false;
saveBtn.textContent = saveBtn.dataset.originalText || '保存';
}
}
}
}
function showMessage(msg, type, duration = 3000) {