Spaces:
Runtime error
Runtime error
Update app.py
Browse files
app.py
CHANGED
|
@@ -278,77 +278,182 @@ def update_all_users():
|
|
| 278 |
@app.route('/upload_audio', methods=['POST'])
|
| 279 |
def upload_audio():
|
| 280 |
global total_audio
|
| 281 |
-
global users
|
| 282 |
-
|
| 283 |
try:
|
| 284 |
data = request.get_json()
|
| 285 |
if not data or 'audio_data' not in data:
|
|
|
|
| 286 |
return jsonify({"error": "音声データがありません"}), 400
|
| 287 |
-
|
| 288 |
-
# リクエストからユーザーリストを取得(指定がなければ現在のusersを使用)
|
| 289 |
-
if 'selected_users' in data and data['selected_users']:
|
| 290 |
-
users = data['selected_users']
|
| 291 |
-
print(f"選択されたユーザー: {users}")
|
| 292 |
|
| 293 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 294 |
return jsonify({"error": "選択されたユーザーがいません"}), 400
|
| 295 |
|
| 296 |
# Base64デコードして音声バイナリを取得
|
| 297 |
audio_binary = base64.b64decode(data['audio_data'])
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
|
|
|
| 301 |
os.makedirs(audio_dir, exist_ok=True)
|
| 302 |
audio_path = os.path.join(audio_dir, f"{upload_name}.wav")
|
| 303 |
with open(audio_path, 'wb') as f:
|
| 304 |
f.write(audio_binary)
|
| 305 |
-
|
| 306 |
-
|
| 307 |
-
|
| 308 |
-
|
| 309 |
-
|
| 310 |
-
os.
|
| 311 |
-
|
|
|
|
| 312 |
# 各ユーザーの参照音声ファイルのパスをリストに格納
|
| 313 |
reference_paths = []
|
| 314 |
-
|
|
|
|
| 315 |
try:
|
| 316 |
-
ref_path = os.path.join(
|
| 317 |
-
|
| 318 |
-
# クラウドから取得
|
| 319 |
-
download_from_cloud(f"{user}.wav", ref_path)
|
| 320 |
-
print(f"クラウドから {user}.wav をダウンロードしました")
|
| 321 |
-
|
| 322 |
if not os.path.exists(ref_path):
|
| 323 |
-
|
| 324 |
-
|
| 325 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 326 |
except Exception as e:
|
| 327 |
-
|
| 328 |
-
|
| 329 |
-
|
| 330 |
-
|
| 331 |
-
|
| 332 |
-
|
| 333 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 334 |
# 各メンバーのrateを計算
|
| 335 |
-
|
| 336 |
-
|
| 337 |
-
|
| 338 |
-
|
| 339 |
-
|
| 340 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 341 |
else:
|
| 342 |
-
|
| 343 |
-
|
| 344 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 345 |
total_time = matched_time + unmatched_time
|
| 346 |
rate = (matched_time / total_time) * 100 if total_time > 0 else 0
|
| 347 |
-
|
| 348 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 349 |
except Exception as e:
|
| 350 |
-
print("
|
| 351 |
-
|
|
|
|
|
|
|
| 352 |
|
| 353 |
# ユーザー選択画面(テンプレート: userSelect.html)
|
| 354 |
@app.route('/')
|
|
|
|
| 278 |
@app.route('/upload_audio', methods=['POST'])
|
| 279 |
def upload_audio():
|
| 280 |
global total_audio
|
| 281 |
+
global users # グローバル変数のusersを更新する場合
|
| 282 |
+
|
| 283 |
try:
|
| 284 |
data = request.get_json()
|
| 285 |
if not data or 'audio_data' not in data:
|
| 286 |
+
print("エラー: リクエストに audio_data が含まれていません。")
|
| 287 |
return jsonify({"error": "音声データがありません"}), 400
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 288 |
|
| 289 |
+
# リクエストからユーザーリストを取得(指定がなければ現在のusersを使用)
|
| 290 |
+
# リクエストごとにユーザーを指定する方が安全
|
| 291 |
+
request_users = data.get('selected_users', []) # .getでキーが存在しない場合も安全に
|
| 292 |
+
if request_users:
|
| 293 |
+
current_users = request_users # リクエストで指定されたユーザーを使用
|
| 294 |
+
print(f"リクエストから選択されたユーザー: {current_users}")
|
| 295 |
+
else:
|
| 296 |
+
# グローバル変数を使う場合(非推奨)
|
| 297 |
+
# current_users = users
|
| 298 |
+
# print(f"グローバル変数からユーザーを使用: {current_users}")
|
| 299 |
+
# グローバル変数ではなく、エラーにする方が安全
|
| 300 |
+
print("エラー: リクエストに selected_users が指定されていません。")
|
| 301 |
+
return jsonify({"error": "選択されたユーザーが指定されていません"}), 400
|
| 302 |
+
|
| 303 |
+
if not current_users:
|
| 304 |
+
print("エラー: 処理対象のユーザーがいません。")
|
| 305 |
return jsonify({"error": "選択されたユーザーがいません"}), 400
|
| 306 |
|
| 307 |
# Base64デコードして音声バイナリを取得
|
| 308 |
audio_binary = base64.b64decode(data['audio_data'])
|
| 309 |
+
|
| 310 |
+
# 一時ファイルに音声を保存
|
| 311 |
+
upload_name = 'uploaded_audio_segment' # 一時ファイル名
|
| 312 |
+
audio_dir = "/tmp/justalk_audio_data" # 一時ディレクトリ名(環境に合わせて変更可)
|
| 313 |
os.makedirs(audio_dir, exist_ok=True)
|
| 314 |
audio_path = os.path.join(audio_dir, f"{upload_name}.wav")
|
| 315 |
with open(audio_path, 'wb') as f:
|
| 316 |
f.write(audio_binary)
|
| 317 |
+
print(f"一時音声ファイルを保存: {audio_path}")
|
| 318 |
+
|
| 319 |
+
print(f"処理を実行するユーザー: {current_users}")
|
| 320 |
+
|
| 321 |
+
# 参照音声用の一時ディレクトリ
|
| 322 |
+
temp_ref_dir = os.path.join(audio_dir, "base_audio")
|
| 323 |
+
os.makedirs(temp_ref_dir, exist_ok=True)
|
| 324 |
+
|
| 325 |
# 各ユーザーの参照音声ファイルのパスをリストに格納
|
| 326 |
reference_paths = []
|
| 327 |
+
missing_files = []
|
| 328 |
+
for user in current_users:
|
| 329 |
try:
|
| 330 |
+
ref_path = os.path.join(temp_ref_dir, f"{user}.wav")
|
| 331 |
+
# 参照ファイルがローカルになければクラウドから取得試行
|
|
|
|
|
|
|
|
|
|
|
|
|
| 332 |
if not os.path.exists(ref_path):
|
| 333 |
+
print(f"参照音声 {ref_path} がローカルにありません。ダウンロードを試みます...")
|
| 334 |
+
if not download_from_cloud(f"{user}.wav", ref_path):
|
| 335 |
+
print(f"エラー: {user}.wav のダウンロードに失敗しました。")
|
| 336 |
+
missing_files.append(user)
|
| 337 |
+
continue # 次のユーザーへ
|
| 338 |
+
else:
|
| 339 |
+
print(f"クラウドから {user}.wav を {ref_path} にダウンロードしました")
|
| 340 |
+
|
| 341 |
+
# 再度存在確認 (ダウンロード成功したか)
|
| 342 |
+
if os.path.exists(ref_path):
|
| 343 |
+
reference_paths.append(ref_path)
|
| 344 |
+
else:
|
| 345 |
+
# 最終的にファイルが見つからなかった場合
|
| 346 |
+
print(f"エラー: ユーザー '{user}' の参照音���ファイルが見つかりません: {ref_path}")
|
| 347 |
+
missing_files.append(user)
|
| 348 |
+
|
| 349 |
except Exception as e:
|
| 350 |
+
print(f"エラー: ユーザー '{user}' の参照音声準備中にエラーが発生しました: {e}")
|
| 351 |
+
# エラーが発生したユーザーをリストに追加
|
| 352 |
+
missing_files.append(user)
|
| 353 |
+
# エラーの詳細をログに出力
|
| 354 |
+
traceback.print_exc()
|
| 355 |
+
|
| 356 |
+
# 必要な参照ファイルが不足している場合はエラーを返す
|
| 357 |
+
if missing_files:
|
| 358 |
+
return jsonify({"error": f"一部ユーザーの参照音声が見つかりません: {', '.join(missing_files)}"}), 500
|
| 359 |
+
# 処理に必要なユーザー数と参照ファイル数が一致しない場合もエラー
|
| 360 |
+
if len(reference_paths) != len(current_users):
|
| 361 |
+
return jsonify({"error": f"参照音声ファイルの数({len(reference_paths)})がユーザー数({len(current_users)})と一致しません"}), 500
|
| 362 |
+
|
| 363 |
+
|
| 364 |
+
# --- ユーザー数に応じて処理分岐 ---
|
| 365 |
+
if len(current_users) > 1:
|
| 366 |
+
# --- 複数人処理 ---
|
| 367 |
+
print(f"複数人 ({len(current_users)}人) の音声処理を開始します。")
|
| 368 |
+
try:
|
| 369 |
+
matched_times, merged_segments = process.process_multi_audio(
|
| 370 |
+
reference_paths, audio_path, current_users, threshold=0.05
|
| 371 |
+
)
|
| 372 |
+
# total_audio = transcripter.save_marged_segments(merged_segments) # 必要なら有効化
|
| 373 |
+
except Exception as proc_e:
|
| 374 |
+
print(f"エラー: process_multi_audio でエラーが発生しました: {proc_e}")
|
| 375 |
+
traceback.print_exc()
|
| 376 |
+
return jsonify({"error": "音声処理中にエラーが発生しました(multi)", "details": str(proc_e)}), 500
|
| 377 |
+
|
| 378 |
# 各メンバーのrateを計算
|
| 379 |
+
total_matched_time = sum(matched_times) # 発話時間の合計
|
| 380 |
+
user_rates = {} # { 'ユーザー名': rate } 形式の辞書
|
| 381 |
+
|
| 382 |
+
print(f"各ユーザーの発話時間 (秒): {dict(zip(current_users, matched_times))}")
|
| 383 |
+
print(f"発話時間の合計 (秒): {total_matched_time:.2f}")
|
| 384 |
+
|
| 385 |
+
# 各ユーザーの割合を計算
|
| 386 |
+
for i in range(len(current_users)):
|
| 387 |
+
user = current_users[i]
|
| 388 |
+
time = matched_times[i]
|
| 389 |
+
# 発話合計時間が0より大きい場合のみ割合計算
|
| 390 |
+
rate = (time / total_matched_time) * 100 if total_matched_time > 0 else 0
|
| 391 |
+
# 念のため rate が 0 未満にならないようにする
|
| 392 |
+
user_rates[user] = max(0, rate)
|
| 393 |
+
|
| 394 |
+
print(f"計算直後の user_rates: {user_rates}")
|
| 395 |
+
|
| 396 |
+
# --- 'その他' の計算と追加 ---
|
| 397 |
+
current_total_rate = sum(user_rates.values()) # 計算されたレートの合計
|
| 398 |
+
print(f"計算後の合計レート: {current_total_rate:.2f}%")
|
| 399 |
+
|
| 400 |
+
# 合計が100%未満の場合 (浮動小数点誤差を考慮)
|
| 401 |
+
# かつ合計が負でないことを確認(通常ありえないが念のため)
|
| 402 |
+
if current_total_rate < 99.99 and current_total_rate >= 0:
|
| 403 |
+
other_rate = 100.0 - current_total_rate
|
| 404 |
+
user_rates['その他'] = other_rate # 'その他' を追加
|
| 405 |
+
print(f"'その他' ({other_rate:.2f}%) を追加しました。")
|
| 406 |
+
|
| 407 |
+
# オプション: 合計が100%をわずかに超える場合の正規化 (必要に応じてコメント解除)
|
| 408 |
+
elif current_total_rate > 100.01:
|
| 409 |
+
print(f"警告: 合計レートが {current_total_rate:.2f}% で100%を超えました。正規化します。")
|
| 410 |
+
factor = 100.0 / current_total_rate
|
| 411 |
+
normalized_rates = {}
|
| 412 |
+
temp_sum = 0
|
| 413 |
+
keys = list(user_rates.keys())
|
| 414 |
+
for i, user in enumerate(keys):
|
| 415 |
+
if i < len(keys) - 1:
|
| 416 |
+
normalized_rate = user_rates[user] * factor
|
| 417 |
+
normalized_rates[user] = normalized_rate
|
| 418 |
+
temp_sum += normalized_rate
|
| 419 |
+
else:
|
| 420 |
+
normalized_rates[user] = max(0, 100.0 - temp_sum) # 最後の要素で調整、0未満防止
|
| 421 |
+
user_rates = normalized_rates
|
| 422 |
+
print(f"正規化後の user_rates: {user_rates}")
|
| 423 |
+
|
| 424 |
+
print(f"最終的に返す user_rates: {user_rates}")
|
| 425 |
+
# React側が扱いやすい user_rates 形式で返す
|
| 426 |
+
return jsonify({"user_rates": user_rates}), 200
|
| 427 |
+
|
| 428 |
else:
|
| 429 |
+
# --- 単一ユーザー処理 ---
|
| 430 |
+
print(f"単一ユーザー ({current_users[0]}) の音声処理を開始します。")
|
| 431 |
+
try:
|
| 432 |
+
matched_time, unmatched_time, merged_segments = process.process_audio(
|
| 433 |
+
reference_paths[0], audio_path, current_users[0], threshold=0.05
|
| 434 |
+
)
|
| 435 |
+
# total_audio = transcripter.save_marged_segments(merged_segments) # 必要なら有効化
|
| 436 |
+
except Exception as proc_e:
|
| 437 |
+
print(f"エラー: process_audio でエラーが発生しました: {proc_e}")
|
| 438 |
+
traceback.print_exc()
|
| 439 |
+
return jsonify({"error": "音声処理中にエラーが発生しました(single)", "details": str(proc_e)}), 500
|
| 440 |
+
|
| 441 |
total_time = matched_time + unmatched_time
|
| 442 |
rate = (matched_time / total_time) * 100 if total_time > 0 else 0
|
| 443 |
+
# レートを 0-100 の範囲に収める
|
| 444 |
+
rate = max(0, min(100, rate))
|
| 445 |
+
silent_rate = 100.0 - rate
|
| 446 |
+
|
| 447 |
+
# シングルユーザーでも user_rates 形式で統一して返す
|
| 448 |
+
user_rates = {current_users[0]: rate, '無音': silent_rate}
|
| 449 |
+
print(f"単一ユーザー、user_rates形式で返す: {user_rates}")
|
| 450 |
+
return jsonify({"user_rates": user_rates}), 200
|
| 451 |
+
|
| 452 |
except Exception as e:
|
| 453 |
+
print(f"エラー: /upload_audio の処理中に予期せぬエラーが発生しました: {e}")
|
| 454 |
+
# エラーの詳細をスタックトレース付きでログに出力
|
| 455 |
+
traceback.print_exc()
|
| 456 |
+
return jsonify({"error": "サーバー内部エラーが発生しました", "details": str(e)}), 500
|
| 457 |
|
| 458 |
# ユーザー選択画面(テンプレート: userSelect.html)
|
| 459 |
@app.route('/')
|