159 lines
5.5 KiB
Python
159 lines
5.5 KiB
Python
"""
|
|
オペレーター
|
|
ユーザーが実行できる操作を定義
|
|
"""
|
|
|
|
import bpy
|
|
from bpy.types import Operator
|
|
from .voicevox import VoiceVoxAPI
|
|
from .subtitles import SubtitleManager
|
|
|
|
|
|
class VOICEVOX_OT_test_connection(Operator):
|
|
"""VoiceVoxエンジンへの接続をテスト"""
|
|
bl_idname = "voicevox.test_connection"
|
|
bl_label = "Test Connection"
|
|
bl_description = "VoiceVoxエンジンへの接続をテストします"
|
|
bl_options = {'REGISTER'}
|
|
|
|
def execute(self, context):
|
|
props = context.scene.voicevox
|
|
api = VoiceVoxAPI(props.voicevox_host, props.voicevox_port)
|
|
|
|
if api.test_connection():
|
|
self.report({'INFO'}, "VoiceVoxエンジンに接続成功!")
|
|
return {'FINISHED'}
|
|
else:
|
|
self.report({'ERROR'}, "VoiceVoxエンジンに接続できません")
|
|
return {'CANCELLED'}
|
|
|
|
|
|
class VOICEVOX_OT_generate_speech(Operator):
|
|
"""VoiceVoxで音声を生成"""
|
|
bl_idname = "voicevox.generate_speech"
|
|
bl_label = "Generate Speech"
|
|
bl_description = "VoiceVoxを使用して音声を生成します"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
def execute(self, context):
|
|
props = context.scene.voicevox
|
|
|
|
if not props.text:
|
|
self.report({'ERROR'}, "テキストを入力してください")
|
|
return {'CANCELLED'}
|
|
|
|
try:
|
|
# VoiceVox APIで音声生成
|
|
print(f"[VoiceVox] 音声生成開始: '{props.text}'")
|
|
print(f"[VoiceVox] 接続先: {props.voicevox_host}:{props.voicevox_port}")
|
|
|
|
api = VoiceVoxAPI(props.voicevox_host, props.voicevox_port)
|
|
|
|
output_dir = bpy.path.abspath(props.output_directory)
|
|
print(f"[VoiceVox] 出力先: {output_dir}")
|
|
|
|
audio_file = api.generate_speech(
|
|
text=props.text,
|
|
speaker_id=props.speaker_id,
|
|
speed_scale=props.speed_scale,
|
|
pitch_scale=props.pitch_scale,
|
|
intonation_scale=props.intonation_scale,
|
|
volume_scale=props.volume_scale,
|
|
output_dir=output_dir,
|
|
)
|
|
|
|
if not audio_file:
|
|
error_msg = f"音声生成に失敗: {api.last_error if api.last_error else '不明なエラー'}"
|
|
self.report({'ERROR'}, error_msg)
|
|
print(f"[VoiceVox] {error_msg}")
|
|
return {'CANCELLED'}
|
|
|
|
self.report({'INFO'}, f"音声生成完了: {audio_file}")
|
|
print(f"[VoiceVox] 音声生成完了: {audio_file}")
|
|
|
|
# シーケンサーに自動追加
|
|
if props.auto_add_to_sequencer:
|
|
self._add_to_sequencer(context, audio_file, props.text)
|
|
|
|
return {'FINISHED'}
|
|
|
|
except Exception as e:
|
|
import traceback
|
|
error_msg = f"音声生成エラー: {str(e)}"
|
|
print(f"[VoiceVox Error] {error_msg}")
|
|
traceback.print_exc()
|
|
self.report({'ERROR'}, error_msg)
|
|
return {'CANCELLED'}
|
|
|
|
def _add_to_sequencer(self, context, audio_file, text):
|
|
"""音声と字幕をシーケンサーに追加"""
|
|
# シーケンスエディタのエリアを取得または作成
|
|
if not context.scene.sequence_editor:
|
|
context.scene.sequence_editor_create()
|
|
|
|
seq_editor = context.scene.sequence_editor
|
|
frame_current = context.scene.frame_current
|
|
|
|
# 音声を追加
|
|
audio_strip = seq_editor.strips.new_sound(
|
|
name="VoiceVox Audio",
|
|
filepath=audio_file,
|
|
channel=props.audio_channel,
|
|
frame_start=frame_current,
|
|
)
|
|
|
|
# 音声の長さを取得(フレーム数)
|
|
audio_duration_frames = audio_strip.frame_final_duration
|
|
print(f"[VoiceVox] 音声の長さ: {audio_duration_frames} フレーム")
|
|
|
|
# 字幕を追加(音声の長さに合わせる)
|
|
props = context.scene.voicevox
|
|
subtitle_mgr = SubtitleManager()
|
|
subtitle_mgr.add_subtitle(
|
|
context=context,
|
|
text=text,
|
|
frame_start=frame_current,
|
|
duration_frames=audio_duration_frames, # 音声の長さに合わせる
|
|
font_size=props.subtitle_font_size,
|
|
position_y=props.subtitle_position_y,
|
|
channel=props.subtitle_channel,
|
|
)
|
|
|
|
|
|
class VOICEVOX_OT_add_subtitle(Operator):
|
|
"""字幕のみを追加"""
|
|
bl_idname = "voicevox.add_subtitle"
|
|
bl_label = "Add Subtitle Only"
|
|
bl_description = "字幕のみをシーケンサーに追加します"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
def execute(self, context):
|
|
props = context.scene.voicevox
|
|
|
|
if not props.text:
|
|
self.report({'ERROR'}, "テキストを入力してください")
|
|
return {'CANCELLED'}
|
|
|
|
if not context.scene.sequence_editor:
|
|
context.scene.sequence_editor_create()
|
|
|
|
frame_current = context.scene.frame_current
|
|
|
|
# 字幕のみの場合はデフォルト3秒
|
|
default_duration_sec = 3.0
|
|
duration_frames = int(default_duration_sec * context.scene.render.fps)
|
|
|
|
subtitle_mgr = SubtitleManager()
|
|
subtitle_mgr.add_subtitle(
|
|
context=context,
|
|
text=props.text,
|
|
frame_start=frame_current,
|
|
duration_frames=duration_frames,
|
|
font_size=props.subtitle_font_size,
|
|
position_y=props.subtitle_position_y,
|
|
channel=props.subtitle_channel,
|
|
)
|
|
|
|
self.report({'INFO'}, "字幕を追加しました")
|
|
return {'FINISHED'}
|