Commit
·
2311e41
1
Parent(s):
3ff2783
kan
Browse files- transcribe_cli.py +164 -59
transcribe_cli.py
CHANGED
|
@@ -11,6 +11,7 @@ import json
|
|
| 11 |
from typing import List, Tuple, Optional, Set # Python 3.9+ では Optional, Set は typing から不要な場合あり
|
| 12 |
import argparse
|
| 13 |
import time # ★処理時間計測のために追加
|
|
|
|
| 14 |
from nemo.collections.asr.models import ASRModel # NeMo ASRモデル
|
| 15 |
|
| 16 |
# --- グローバル設定 ---
|
|
@@ -252,32 +253,87 @@ def write_vtt(segments: List, words: List, path: Path):
|
|
| 252 |
h, rem = divmod(int(t_float), 3600); m, s = divmod(rem, 60)
|
| 253 |
ms = int((t_float - int(t_float)) * 1000)
|
| 254 |
return f"{h:02}:{m:02}:{s:02}.{ms:03}"
|
|
|
|
| 255 |
with open(path, "w", encoding="utf-8") as f:
|
| 256 |
f.write("WEBVTT\n\n")
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
|
| 261 |
-
|
|
|
|
|
|
|
|
|
|
| 262 |
for i, seg_list in enumerate(segments, 1):
|
| 263 |
-
|
| 264 |
-
|
| 265 |
return
|
| 266 |
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
|
| 276 |
-
|
| 277 |
-
if
|
| 278 |
-
|
| 279 |
-
|
| 280 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 281 |
|
| 282 |
def write_json(segments: List, words: List, path: Path):
|
| 283 |
result = {"segments": []}; word_idx = 0
|
|
@@ -354,7 +410,6 @@ def process_single_file(
|
|
| 354 |
input_file_stem = input_file_path_obj.stem
|
| 355 |
output_and_temp_dir_str = input_file_path_obj.parent.as_posix()
|
| 356 |
|
| 357 |
-
# ★ファイル処理開始時刻を記録
|
| 358 |
file_processing_start_time = time.time()
|
| 359 |
actual_audio_duration_sec: Optional[float] = None
|
| 360 |
success_status = False
|
|
@@ -449,7 +504,6 @@ def process_single_file(
|
|
| 449 |
print(f"エラー: ファイル {input_file_path_obj.name} の処理中にエラーが発生しました: {e}")
|
| 450 |
success_status = False
|
| 451 |
finally:
|
| 452 |
-
# ★ファイル処理時間とサマリーをログに出力
|
| 453 |
file_processing_end_time = time.time()
|
| 454 |
time_taken_seconds = file_processing_end_time - file_processing_start_time
|
| 455 |
proc_m = int(time_taken_seconds // 60)
|
|
@@ -473,7 +527,7 @@ def process_single_file(
|
|
| 473 |
if Path(chunk_f_str).exists():
|
| 474 |
try: os.remove(chunk_f_str); print(f" 一時チャンクファイル {Path(chunk_f_str).name} を削除しました。")
|
| 475 |
except OSError as e_os_chunk: print(f" 警告: 一時チャンクファイル {Path(chunk_f_str).name} の削除に失敗: {e_os_chunk}")
|
| 476 |
-
|
| 477 |
return success_status
|
| 478 |
|
| 479 |
# --- ディレクトリ内ファイルの一括処理関数 ---
|
|
@@ -483,9 +537,7 @@ def batch_process_directory(
|
|
| 483 |
device_to_use: str,
|
| 484 |
output_formats: Optional[List[str]] = None
|
| 485 |
):
|
| 486 |
-
# ★バッチ処理全体の開始時刻
|
| 487 |
batch_start_time = time.time()
|
| 488 |
-
|
| 489 |
if output_formats is None:
|
| 490 |
output_formats_to_use = DEFAULT_OUTPUT_FORMATS
|
| 491 |
else:
|
|
@@ -529,18 +581,17 @@ def batch_process_directory(
|
|
| 529 |
failed_count = 0
|
| 530 |
|
| 531 |
for input_file_to_process_obj in files_to_actually_process:
|
|
|
|
| 532 |
is_skipped_at_batch_level = False
|
| 533 |
-
if "csv" in output_formats_to_use:
|
| 534 |
output_csv_path_check = input_file_to_process_obj.with_suffix('.csv')
|
| 535 |
if output_csv_path_check.exists():
|
| 536 |
-
print(f"\n======== ファイル処理開始: {input_file_to_process_obj.name} ========")
|
| 537 |
print(f"スキップ (バッチレベル): CSV '{output_csv_path_check.name}' は既に存在します。")
|
| 538 |
-
print(f"======== ファイル処理終了 (スキップ): {input_file_to_process_obj.name} ========\n")
|
| 539 |
skipped_due_to_existing_csv_count += 1
|
| 540 |
is_skipped_at_batch_level = True
|
|
|
|
| 541 |
|
| 542 |
if not is_skipped_at_batch_level:
|
| 543 |
-
print(f"\n======== ファイル処理開始: {input_file_to_process_obj.name} ========") # process_single_file に移譲
|
| 544 |
success_flag = process_single_file(
|
| 545 |
input_file_to_process_obj,
|
| 546 |
asr_model_instance,
|
|
@@ -551,6 +602,7 @@ def batch_process_directory(
|
|
| 551 |
processed_successfully_count += 1
|
| 552 |
else:
|
| 553 |
failed_count += 1
|
|
|
|
| 554 |
|
| 555 |
print("\n======== 全ファイルのバッチ処理が完了しました ========")
|
| 556 |
total_considered = len(files_to_actually_process)
|
|
@@ -559,7 +611,6 @@ def batch_process_directory(
|
|
| 559 |
print(f" CSV既存によりスキップされたファイル数: {skipped_due_to_existing_csv_count}")
|
| 560 |
print(f" 処理失敗ファイル数: {failed_count}")
|
| 561 |
|
| 562 |
-
# ★バッチ処理全体の総所要時間を表示
|
| 563 |
batch_end_time = time.time()
|
| 564 |
total_batch_time_seconds = batch_end_time - batch_start_time
|
| 565 |
batch_m = int(total_batch_time_seconds // 60)
|
|
@@ -568,28 +619,73 @@ def batch_process_directory(
|
|
| 568 |
|
| 569 |
# --- スクリプト実行のエントリポイント ---
|
| 570 |
if __name__ == "__main__":
|
| 571 |
-
|
| 572 |
-
|
| 573 |
-
|
| 574 |
-
|
| 575 |
-
|
| 576 |
-
|
| 577 |
-
"
|
| 578 |
-
|
| 579 |
-
|
| 580 |
-
|
| 581 |
-
|
| 582 |
-
|
| 583 |
-
|
| 584 |
-
|
| 585 |
-
|
| 586 |
-
|
| 587 |
-
|
| 588 |
-
|
| 589 |
-
|
| 590 |
-
|
| 591 |
-
|
| 592 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 593 |
else: selected_device = "cuda" if torch.cuda.is_available() else "cpu"
|
| 594 |
print(f"使用デバイス: {selected_device.upper()}")
|
| 595 |
if selected_device == "cuda":
|
|
@@ -606,17 +702,26 @@ if __name__ == "__main__":
|
|
| 606 |
asr_model_main.eval()
|
| 607 |
print(f"モデル '{MODEL_NAME}' のロード完了。")
|
| 608 |
except Exception as model_load_e:
|
| 609 |
-
print(f"致命的エラー: ASRモデル '{MODEL_NAME}' のロードに失敗: {model_load_e}"); exit(1)
|
| 610 |
|
| 611 |
-
output_formats_requested = [fmt.strip().lower() for fmt in
|
| 612 |
final_output_formats_to_use = [fmt for fmt in output_formats_requested if fmt in DEFAULT_OUTPUT_FORMATS]
|
| 613 |
-
if not output_formats_requested and
|
| 614 |
-
print(f"警告: 指定された出力フォーマット '{
|
| 615 |
if not final_output_formats_to_use :
|
| 616 |
print(f"情報: 有効な出力フォーマットが指定されなかったため、デフォルトの全形式 ({','.join(DEFAULT_OUTPUT_FORMATS)}) で出力します。")
|
| 617 |
final_output_formats_to_use = DEFAULT_OUTPUT_FORMATS
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 618 |
|
| 619 |
batch_process_directory(
|
| 620 |
-
|
| 621 |
output_formats=final_output_formats_to_use
|
| 622 |
-
)
|
|
|
|
| 11 |
from typing import List, Tuple, Optional, Set # Python 3.9+ では Optional, Set は typing から不要な場合あり
|
| 12 |
import argparse
|
| 13 |
import time # ★処理時間計測のために追加
|
| 14 |
+
import sys # ★コマンドライン引数チェックのために追加
|
| 15 |
from nemo.collections.asr.models import ASRModel # NeMo ASRモデル
|
| 16 |
|
| 17 |
# --- グローバル設定 ---
|
|
|
|
| 253 |
h, rem = divmod(int(t_float), 3600); m, s = divmod(rem, 60)
|
| 254 |
ms = int((t_float - int(t_float)) * 1000)
|
| 255 |
return f"{h:02}:{m:02}:{s:02}.{ms:03}"
|
| 256 |
+
|
| 257 |
with open(path, "w", encoding="utf-8") as f:
|
| 258 |
f.write("WEBVTT\n\n")
|
| 259 |
+
f.write("STYLE\n")
|
| 260 |
+
f.write("::cue(.current) { color: #ffff00; font-weight: bold; }\n")
|
| 261 |
+
f.write("::cue(.past) { color: #888888; }\n")
|
| 262 |
+
f.write("::cue(.future) { color: #ffffff; }\n")
|
| 263 |
+
f.write("::cue(.line) { background: rgba(0,0,0,0.7); padding: 4px; }\n\n")
|
| 264 |
+
|
| 265 |
+
if not words:
|
| 266 |
+
# フォールバック処理は同じ
|
| 267 |
for i, seg_list in enumerate(segments, 1):
|
| 268 |
+
f.write(f"NOTE Segment {i}\n")
|
| 269 |
+
f.write(f"{sec2vtt(float(seg_list[0]))} --> {sec2vtt(float(seg_list[1]))}\n{seg_list[2]}\n\n")
|
| 270 |
return
|
| 271 |
|
| 272 |
+
# セグメント単位でグループ化してカラオケ風に
|
| 273 |
+
for seg_data in segments:
|
| 274 |
+
seg_start = float(seg_data[0])
|
| 275 |
+
seg_end = float(seg_data[1])
|
| 276 |
+
|
| 277 |
+
# このセグメントに含まれる単語を特定
|
| 278 |
+
segment_words = []
|
| 279 |
+
for word_idx, word_data in enumerate(words):
|
| 280 |
+
word_start = float(word_data[0])
|
| 281 |
+
word_end = float(word_data[1])
|
| 282 |
+
if word_start >= seg_start - 0.1 and word_end <= seg_end + 0.1:
|
| 283 |
+
segment_words.append((word_idx, word_data))
|
| 284 |
+
|
| 285 |
+
if not segment_words:
|
| 286 |
+
continue
|
| 287 |
+
|
| 288 |
+
# セグメント開始時刻から最初の単語開始まで(全て未来色)
|
| 289 |
+
first_word_start = float(segment_words[0][1][0])
|
| 290 |
+
if seg_start < first_word_start - 0.05:
|
| 291 |
+
line_parts = [f'<c.future>{w_data[2]}</c>' for _, w_data in segment_words]
|
| 292 |
+
f.write(f"{sec2vtt(seg_start)} --> {sec2vtt(first_word_start)}\n")
|
| 293 |
+
f.write(f'<c.line>{" ".join(line_parts)}</c>\n\n')
|
| 294 |
+
|
| 295 |
+
# 各単語の処理
|
| 296 |
+
for local_idx, (global_word_idx, word_data) in enumerate(segment_words):
|
| 297 |
+
w_start = float(word_data[0])
|
| 298 |
+
w_end = float(word_data[1])
|
| 299 |
+
|
| 300 |
+
# 単語再生中:現在の単語をハイライト
|
| 301 |
+
line_parts = []
|
| 302 |
+
for i, (_, w_data) in enumerate(segment_words):
|
| 303 |
+
w_text = w_data[2]
|
| 304 |
+
if i == local_idx:
|
| 305 |
+
line_parts.append(f'<c.current>{w_text}</c>')
|
| 306 |
+
elif i < local_idx:
|
| 307 |
+
line_parts.append(f'<c.past>{w_text}</c>')
|
| 308 |
+
else:
|
| 309 |
+
line_parts.append(f'<c.future>{w_text}</c>')
|
| 310 |
+
|
| 311 |
+
f.write(f"{sec2vtt(w_start)} --> {sec2vtt(w_end)}\n")
|
| 312 |
+
f.write(f'<c.line>{" ".join(line_parts)}</c>\n\n')
|
| 313 |
+
|
| 314 |
+
# 単語終了から次の単語開始まで(無音期間):過去・未来のみ
|
| 315 |
+
if local_idx < len(segment_words) - 1: # 最後の単語でない場合
|
| 316 |
+
next_word_start = float(segment_words[local_idx + 1][1][0])
|
| 317 |
+
gap_duration = next_word_start - w_end
|
| 318 |
+
|
| 319 |
+
if gap_duration > 0.05: # 50ms以上の無音期間がある場合
|
| 320 |
+
gap_line_parts = []
|
| 321 |
+
for i, (_, w_data) in enumerate(segment_words):
|
| 322 |
+
w_text = w_data[2]
|
| 323 |
+
if i <= local_idx: # 現在の単語まで(過去)
|
| 324 |
+
gap_line_parts.append(f'<c.past>{w_text}</c>')
|
| 325 |
+
else: # 未来の単語
|
| 326 |
+
gap_line_parts.append(f'<c.future>{w_text}</c>')
|
| 327 |
+
|
| 328 |
+
f.write(f"{sec2vtt(w_end)} --> {sec2vtt(next_word_start)}\n")
|
| 329 |
+
f.write(f'<c.line>{" ".join(gap_line_parts)}</c>\n\n')
|
| 330 |
+
else:
|
| 331 |
+
# 最後の単語終了からセグメント終了まで(全て過去色)
|
| 332 |
+
if w_end < seg_end - 0.05:
|
| 333 |
+
line_parts = [f'<c.past>{w_data[2]}</c>' for _, w_data in segment_words]
|
| 334 |
+
f.write(f"{sec2vtt(w_end)} --> {sec2vtt(seg_end)}\n")
|
| 335 |
+
f.write(f'<c.line>{" ".join(line_parts)}</c>\n\n')
|
| 336 |
+
|
| 337 |
|
| 338 |
def write_json(segments: List, words: List, path: Path):
|
| 339 |
result = {"segments": []}; word_idx = 0
|
|
|
|
| 410 |
input_file_stem = input_file_path_obj.stem
|
| 411 |
output_and_temp_dir_str = input_file_path_obj.parent.as_posix()
|
| 412 |
|
|
|
|
| 413 |
file_processing_start_time = time.time()
|
| 414 |
actual_audio_duration_sec: Optional[float] = None
|
| 415 |
success_status = False
|
|
|
|
| 504 |
print(f"エラー: ファイル {input_file_path_obj.name} の処理中にエラーが発生しました: {e}")
|
| 505 |
success_status = False
|
| 506 |
finally:
|
|
|
|
| 507 |
file_processing_end_time = time.time()
|
| 508 |
time_taken_seconds = file_processing_end_time - file_processing_start_time
|
| 509 |
proc_m = int(time_taken_seconds // 60)
|
|
|
|
| 527 |
if Path(chunk_f_str).exists():
|
| 528 |
try: os.remove(chunk_f_str); print(f" 一時チャンクファイル {Path(chunk_f_str).name} を削除しました。")
|
| 529 |
except OSError as e_os_chunk: print(f" 警告: 一時チャンクファイル {Path(chunk_f_str).name} の削除に失敗: {e_os_chunk}")
|
| 530 |
+
# process_single_file の最後では "ファイル処理終了" のログは batch_process_directory に任せる
|
| 531 |
return success_status
|
| 532 |
|
| 533 |
# --- ディレクトリ内ファイルの一括処理関数 ---
|
|
|
|
| 537 |
device_to_use: str,
|
| 538 |
output_formats: Optional[List[str]] = None
|
| 539 |
):
|
|
|
|
| 540 |
batch_start_time = time.time()
|
|
|
|
| 541 |
if output_formats is None:
|
| 542 |
output_formats_to_use = DEFAULT_OUTPUT_FORMATS
|
| 543 |
else:
|
|
|
|
| 581 |
failed_count = 0
|
| 582 |
|
| 583 |
for input_file_to_process_obj in files_to_actually_process:
|
| 584 |
+
print(f"\n======== ファイル処理開始: {input_file_to_process_obj.name} ========") # 各ファイルの開始ログ
|
| 585 |
is_skipped_at_batch_level = False
|
| 586 |
+
if "csv" in output_formats_to_use:
|
| 587 |
output_csv_path_check = input_file_to_process_obj.with_suffix('.csv')
|
| 588 |
if output_csv_path_check.exists():
|
|
|
|
| 589 |
print(f"スキップ (バッチレベル): CSV '{output_csv_path_check.name}' は既に存在します。")
|
|
|
|
| 590 |
skipped_due_to_existing_csv_count += 1
|
| 591 |
is_skipped_at_batch_level = True
|
| 592 |
+
print(f"======== ファイル処理終了 (スキップ): {input_file_to_process_obj.name} ========\n") # スキップ時の終了ログ
|
| 593 |
|
| 594 |
if not is_skipped_at_batch_level:
|
|
|
|
| 595 |
success_flag = process_single_file(
|
| 596 |
input_file_to_process_obj,
|
| 597 |
asr_model_instance,
|
|
|
|
| 602 |
processed_successfully_count += 1
|
| 603 |
else:
|
| 604 |
failed_count += 1
|
| 605 |
+
# process_single_file内で "ファイル処理終了" ログが出力される
|
| 606 |
|
| 607 |
print("\n======== 全ファイルのバッチ処理が完了しました ========")
|
| 608 |
total_considered = len(files_to_actually_process)
|
|
|
|
| 611 |
print(f" CSV既存によりスキップされたファイル数: {skipped_due_to_existing_csv_count}")
|
| 612 |
print(f" 処理失敗ファイル数: {failed_count}")
|
| 613 |
|
|
|
|
| 614 |
batch_end_time = time.time()
|
| 615 |
total_batch_time_seconds = batch_end_time - batch_start_time
|
| 616 |
batch_m = int(total_batch_time_seconds // 60)
|
|
|
|
| 619 |
|
| 620 |
# --- スクリプト実行のエントリポイント ---
|
| 621 |
if __name__ == "__main__":
|
| 622 |
+
# ★ 引数処理とGUI分岐のための準備
|
| 623 |
+
target_directory_arg: Optional[str] = None
|
| 624 |
+
formats_arg_str: str = ",".join(DEFAULT_OUTPUT_FORMATS) # GUI時のデフォルト
|
| 625 |
+
device_arg_str: Optional[str] = None # GUI時のデフォルト (自動判別)
|
| 626 |
+
|
| 627 |
+
if len(sys.argv) == 1: # コマンドライン引数なしの場合
|
| 628 |
+
print("コマンドライン引数なしで起動されました。GUIでディレクトリを選択します。")
|
| 629 |
+
try:
|
| 630 |
+
import tkinter as tk
|
| 631 |
+
from tkinter import filedialog
|
| 632 |
+
|
| 633 |
+
def get_directory_from_gui_local() -> Optional[str]:
|
| 634 |
+
"""GUIでディレクトリ選択ダイアログを表示し、選択されたパスを返す"""
|
| 635 |
+
root = tk.Tk()
|
| 636 |
+
root.withdraw() # メインウィンドウは表示しない
|
| 637 |
+
# ダイアログを最前面に表示する試み (環境による)
|
| 638 |
+
root.attributes('-topmost', True)
|
| 639 |
+
selected_path = filedialog.askdirectory(title="処理対象のディレクトリを選択してください")
|
| 640 |
+
root.attributes('-topmost', False)
|
| 641 |
+
root.destroy() # Tkinterウィンドウを破棄
|
| 642 |
+
return selected_path if selected_path else None
|
| 643 |
+
|
| 644 |
+
target_directory_arg = get_directory_from_gui_local()
|
| 645 |
+
if not target_directory_arg:
|
| 646 |
+
print("ディレクトリが選択されませんでした。処理を中止します。")
|
| 647 |
+
sys.exit(0) # 正常終了
|
| 648 |
+
# formats_arg_str と device_arg_str は初期化されたデフォルト値を使用
|
| 649 |
+
print(f"GUIで選択されたディレクトリ: {target_directory_arg}")
|
| 650 |
+
print(f"出力フォーマット (デフォルト): {formats_arg_str}")
|
| 651 |
+
# device_arg_strがNoneの場合、後続の処理で自動判別される
|
| 652 |
+
|
| 653 |
+
except ImportError:
|
| 654 |
+
print("エラー: GUIモードに必要なTkinterライブラリが見つかりません。")
|
| 655 |
+
print("Tkinterをインストールするか、コマンドライン引数を使用してスクリプトを実行してください。例:")
|
| 656 |
+
print(f" python {Path(sys.argv[0]).name} /path/to/your/audio_directory")
|
| 657 |
+
sys.exit(1) # エラー終了
|
| 658 |
+
except Exception as e_gui:
|
| 659 |
+
print(f"GUIの表示中に予期せぬエラーが発生しました: {e_gui}")
|
| 660 |
+
sys.exit(1) # エラー終了
|
| 661 |
+
else: # コマンドライン引数がある場合
|
| 662 |
+
parser = argparse.ArgumentParser(
|
| 663 |
+
description="指定されたディレクトリ内の音声/動画ファイルをNVIDIA Parakeet ASRモデルで文字起こしします。\n"
|
| 664 |
+
f"同じ名前のファイルが複数ある場合、{' > '.join(INPUT_PRIORITY_EXTENSIONS)} の優先順位で処理します。",
|
| 665 |
+
formatter_class=argparse.RawTextHelpFormatter
|
| 666 |
+
)
|
| 667 |
+
parser.add_argument( # 最初の引数は必須のディレクトリ
|
| 668 |
+
"target_directory", type=str,
|
| 669 |
+
help="処理対象のファイルが含まれるディレクトリのパス。"
|
| 670 |
+
)
|
| 671 |
+
parser.add_argument(
|
| 672 |
+
"--formats", type=str, default=",".join(DEFAULT_OUTPUT_FORMATS),
|
| 673 |
+
help=(f"出力する文字起こしファイルの形式をカンマ区切りで指定。\n"
|
| 674 |
+
f"例: csv,srt (デフォルト: {','.join(DEFAULT_OUTPUT_FORMATS)})\n"
|
| 675 |
+
f"利用可能な形式: {','.join(DEFAULT_OUTPUT_FORMATS)}")
|
| 676 |
+
)
|
| 677 |
+
parser.add_argument(
|
| 678 |
+
"--device", type=str, default=None, choices=['cuda', 'cpu'],
|
| 679 |
+
help="使用するデバイスを指定 (cuda または cpu)。指定がなければ自動判別。"
|
| 680 |
+
)
|
| 681 |
+
args = parser.parse_args() # sys.argv[1:] から解析
|
| 682 |
+
|
| 683 |
+
target_directory_arg = args.target_directory
|
| 684 |
+
formats_arg_str = args.formats
|
| 685 |
+
device_arg_str = args.device
|
| 686 |
+
|
| 687 |
+
# --- 共通のセットアップ処理 ---
|
| 688 |
+
if device_arg_str: selected_device = device_arg_str
|
| 689 |
else: selected_device = "cuda" if torch.cuda.is_available() else "cpu"
|
| 690 |
print(f"使用デバイス: {selected_device.upper()}")
|
| 691 |
if selected_device == "cuda":
|
|
|
|
| 702 |
asr_model_main.eval()
|
| 703 |
print(f"モデル '{MODEL_NAME}' のロード完了。")
|
| 704 |
except Exception as model_load_e:
|
| 705 |
+
print(f"致命的エラー: ASRモデル '{MODEL_NAME}' のロードに失敗: {model_load_e}"); sys.exit(1)
|
| 706 |
|
| 707 |
+
output_formats_requested = [fmt.strip().lower() for fmt in formats_arg_str.split(',') if fmt.strip()]
|
| 708 |
final_output_formats_to_use = [fmt for fmt in output_formats_requested if fmt in DEFAULT_OUTPUT_FORMATS]
|
| 709 |
+
if not output_formats_requested and formats_arg_str:
|
| 710 |
+
print(f"警告: 指定された出力フォーマット '{formats_arg_str}' は無効です。")
|
| 711 |
if not final_output_formats_to_use :
|
| 712 |
print(f"情報: 有効な出力フォーマットが指定されなかったため、デフォルトの全形式 ({','.join(DEFAULT_OUTPUT_FORMATS)}) で出力します。")
|
| 713 |
final_output_formats_to_use = DEFAULT_OUTPUT_FORMATS
|
| 714 |
+
|
| 715 |
+
# target_directory_arg が None でないことを確認 (GUIキャンセル時など)
|
| 716 |
+
if not target_directory_arg:
|
| 717 |
+
print("エラー: 処理対象のディレクトリが指定されていません。処理を中止します。")
|
| 718 |
+
sys.exit(1)
|
| 719 |
+
|
| 720 |
+
if not asr_model_main: # 通常、モデルロード失敗で既にexitしているはずだが念のため
|
| 721 |
+
print("致命的エラー: ASRモデルがロードされていません。処理を中止します。")
|
| 722 |
+
sys.exit(1)
|
| 723 |
|
| 724 |
batch_process_directory(
|
| 725 |
+
target_directory_arg, asr_model_main, selected_device,
|
| 726 |
output_formats=final_output_formats_to_use
|
| 727 |
+
)
|