refactor: clean prints add LICENCE
This commit is contained in:
parent
4973220a3f
commit
25a555cc29
7
LICENSE
Normal file
7
LICENSE
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
Copyright 2026 Hare
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
92
README.md
92
README.md
|
|
@ -2,48 +2,6 @@
|
|||
|
||||
Blender 5.0 向けの VoiceVox 音声合成と字幕表示を統合したアドオンです。
|
||||
|
||||
## 機能
|
||||
|
||||
- **音声合成**: VoiceVox エンジンを使用してテキストから音声を生成
|
||||
- **字幕表示**: 生成した音声と同期する字幕をシーケンサーに追加
|
||||
- **カスタマイズ可能**: 話者、話速、音高、抑揚、音量などを調整可能
|
||||
- **自動統合**: 生成した音声と字幕を自動的にシーケンサーに追加
|
||||
|
||||
## 必要なものeee
|
||||
|
||||
- Blender 5.0 以上
|
||||
- VoiceVox エンジン(ローカルで実行中であること)
|
||||
- https://voicevox.hiroshiba.jp/ からダウンロード
|
||||
- デフォルトでは `127.0.0.1:50021` で動作
|
||||
|
||||
## インストール
|
||||
|
||||
### 開発環境のセットアップ(Nix + direnv)
|
||||
|
||||
```bash
|
||||
# direnv を許可
|
||||
direnv allow
|
||||
|
||||
# 開発環境に入る(自動的に有効化されます)
|
||||
# または手動で: nix develop
|
||||
```
|
||||
|
||||
### Blender へのインストール
|
||||
|
||||
1. このディレクトリ全体を Blender のアドオンフォルダにシンボリックリンク:
|
||||
```bash
|
||||
ln -s $(pwd) ~/.config/blender/5.0/scripts/addons/blender-voicevox-pl
|
||||
```
|
||||
|
||||
2. または、Blender の UI からインストール:
|
||||
- Edit > Preferences > Add-ons
|
||||
- Install ボタンをクリック
|
||||
- このディレクトリを zip 化したファイルを選択
|
||||
|
||||
3. アドオンを有効化:
|
||||
- "VoiceVox TTS & Subtitles" を検索
|
||||
- チェックボックスをオンにする
|
||||
|
||||
## 使い方
|
||||
|
||||
1. **VoiceVox エンジンを起動**
|
||||
|
|
@ -51,7 +9,7 @@ direnv allow
|
|||
- VoiceVox エンジンをコマンドラインで起動
|
||||
|
||||
2. **Blender でシーケンスエディタを開く**
|
||||
- ウィンドウタイプを "Video Sequencing" に変更
|
||||
- "Video Editing"ワークスペース下部、"Video Sequencer"エディタを開く
|
||||
|
||||
3. **VoiceVox パネルを開く**
|
||||
- サイドバー(N キー)の "VoiceVox" タブをクリック
|
||||
|
|
@ -64,54 +22,6 @@ direnv allow
|
|||
- 話者や音声パラメータを調整
|
||||
- "Generate Speech & Subtitle" ボタンをクリック
|
||||
|
||||
## 開発
|
||||
|
||||
### プロジェクト構成
|
||||
|
||||
```
|
||||
.
|
||||
├── __init__.py # アドオンのエントリーポイント
|
||||
├── operators.py # ユーザー操作の実装
|
||||
├── panels.py # UI パネルの定義
|
||||
├── properties.py # プロパティの定義
|
||||
├── voicevox.py # VoiceVox API 連携
|
||||
├── subtitles.py # 字幕管理
|
||||
├── flake.nix # Nix 開発環境設定
|
||||
└── README.md # このファイル
|
||||
```
|
||||
|
||||
### Blender でのテスト
|
||||
|
||||
```bash
|
||||
# バックグラウンドでテスト
|
||||
blender --background --python-expr "import bpy; bpy.ops.preferences.addon_enable(module='blender-voicevox-pl')"
|
||||
|
||||
# GUI でテスト(開発用)
|
||||
blender
|
||||
```
|
||||
|
||||
## トラブルシューティング
|
||||
|
||||
### VoiceVox に接続できない
|
||||
|
||||
- VoiceVox エンジンが起動しているか確認
|
||||
- ホスト・ポート設定が正しいか確認(デフォルト: 127.0.0.1:50021)
|
||||
- ファイアウォールがブロックしていないか確認
|
||||
|
||||
### 音声ファイルが生成されない
|
||||
|
||||
- 出力ディレクトリの書き込み権限を確認
|
||||
- VoiceVox エンジンのログを確認
|
||||
|
||||
### 字幕が表示されない
|
||||
|
||||
- シーケンスエディタで正しいチャンネルを表示しているか確認
|
||||
- レンダー設定で字幕のストリップが有効になっているか確認
|
||||
|
||||
## ライセンス
|
||||
|
||||
MIT License
|
||||
|
||||
## 作者
|
||||
|
||||
Your Name
|
||||
|
|
|
|||
11
__init__.py
11
__init__.py
|
|
@ -5,7 +5,7 @@ VoiceVoxを使用した音声合成と字幕表示を同時に行うBlenderア
|
|||
|
||||
bl_info = {
|
||||
"name": "VoiceVox TTS & Subtitles",
|
||||
"author": "Your Name",
|
||||
"author": "Hare",
|
||||
"version": (1, 0, 0),
|
||||
"blender": (5, 0, 0),
|
||||
"location": "View3D > Sidebar > VoiceVox",
|
||||
|
|
@ -40,16 +40,12 @@ def register():
|
|||
for cls in classes:
|
||||
bpy.utils.register_class(cls)
|
||||
|
||||
# シーンにプロパティを追加
|
||||
bpy.types.Scene.voicevox = bpy.props.PointerProperty(type=properties.VoiceVoxProperties)
|
||||
|
||||
# 初回起動時に話者リストを取得
|
||||
try:
|
||||
properties.update_speaker_cache()
|
||||
except:
|
||||
pass # 初回はVoiceVoxが起動していない可能性があるのでスキップ
|
||||
|
||||
print("VoiceVox Plugin registered")
|
||||
pass
|
||||
|
||||
|
||||
def unregister():
|
||||
|
|
@ -57,11 +53,8 @@ def unregister():
|
|||
for cls in reversed(classes):
|
||||
bpy.utils.unregister_class(cls)
|
||||
|
||||
# シーンからプロパティを削除
|
||||
del bpy.types.Scene.voicevox
|
||||
|
||||
print("VoiceVox Plugin unregistered")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
|
|
|
|||
61
operators.py
61
operators.py
|
|
@ -23,73 +23,53 @@ def get_text_strip_settings(context):
|
|||
props = context.scene.voicevox
|
||||
text_strip = None
|
||||
|
||||
# 1. まずリファレンステキストストリップをチェック
|
||||
if props.reference_text_strip:
|
||||
for strip in seq_editor.strips_all:
|
||||
if strip.name == props.reference_text_strip and strip.type == 'TEXT':
|
||||
text_strip = strip
|
||||
print(f"[VoiceVox] リファレンステキスト '{strip.name}' を使用")
|
||||
break
|
||||
|
||||
# 2. リファレンスが見つからない場合は選択されているストリップを使用
|
||||
if not text_strip:
|
||||
if seq_editor.active_strip and seq_editor.active_strip.type == 'TEXT':
|
||||
text_strip = seq_editor.active_strip
|
||||
print(f"[VoiceVox] アクティブなテキスト '{text_strip.name}' を使用")
|
||||
else:
|
||||
# 選択されているストリップからテキストを探す
|
||||
for strip in seq_editor.strips_all:
|
||||
if strip.select and strip.type == 'TEXT':
|
||||
text_strip = strip
|
||||
print(f"[VoiceVox] 選択されたテキスト '{text_strip.name}' を使用")
|
||||
break
|
||||
|
||||
if not text_strip:
|
||||
return None
|
||||
|
||||
# すべてのプロパティを動的に抽出
|
||||
settings = {}
|
||||
|
||||
# ストリップ固有のプロパティ(コピーしないもの)
|
||||
skip_properties = {
|
||||
'name', 'type', 'frame_start', 'frame_final_start',
|
||||
'frame_final_end', 'frame_final_duration', 'frame_duration',
|
||||
'frame_offset_start', 'frame_offset_end', 'frame_still_start',
|
||||
'frame_still_end', 'select', 'select_left_handle',
|
||||
'select_right_handle', 'mute', 'lock', 'text', # textは別途設定
|
||||
'channel', # channelはUIの設定を優先
|
||||
'select_right_handle', 'mute', 'lock', 'text',
|
||||
'channel',
|
||||
}
|
||||
|
||||
# bl_rna.propertiesから全プロパティを取得
|
||||
if hasattr(text_strip, 'bl_rna') and hasattr(text_strip.bl_rna, 'properties'):
|
||||
# デバッグ: すべてのプロパティ名を出力
|
||||
all_prop_names = [p.identifier for p in text_strip.bl_rna.properties]
|
||||
print(f"[VoiceVox Debug] 利用可能なプロパティ: {all_prop_names}")
|
||||
|
||||
for prop in text_strip.bl_rna.properties:
|
||||
prop_name = prop.identifier
|
||||
|
||||
# スキップするプロパティ
|
||||
if prop_name in skip_properties:
|
||||
print(f"[VoiceVox Debug] スキップ(skip_properties): {prop_name}")
|
||||
continue
|
||||
|
||||
# プロパティの値を取得
|
||||
try:
|
||||
value = getattr(text_strip, prop_name)
|
||||
|
||||
# locationは特別処理
|
||||
if prop_name == 'location':
|
||||
try:
|
||||
settings['position_x'] = value[0]
|
||||
settings['position_y'] = value[1]
|
||||
except (TypeError, IndexError):
|
||||
pass
|
||||
# transformは子プロパティを個別にコピー(readonly だが子プロパティは書き込み可能)
|
||||
elif prop_name == 'transform':
|
||||
print(f"[VoiceVox Debug] transform プロパティを発見: {value}")
|
||||
if hasattr(value, 'bl_rna') and hasattr(value.bl_rna, 'properties'):
|
||||
print(f"[VoiceVox Debug] transform の子プロパティを列挙中...")
|
||||
for transform_prop in value.bl_rna.properties:
|
||||
transform_prop_name = transform_prop.identifier
|
||||
if transform_prop.is_readonly:
|
||||
|
|
@ -97,23 +77,18 @@ def get_text_strip_settings(context):
|
|||
try:
|
||||
transform_value = getattr(value, transform_prop_name)
|
||||
settings[f'transform_{transform_prop_name}'] = transform_value
|
||||
print(f"[VoiceVox] コピー: transform.{transform_prop_name} = {transform_value}")
|
||||
except Exception as e:
|
||||
print(f"[VoiceVox] transform.{transform_prop_name} の取得をスキップ: {e}")
|
||||
else:
|
||||
print(f"[VoiceVox Debug] transform に bl_rna.properties がありません")
|
||||
pass
|
||||
else:
|
||||
# 通常のプロパティ: readonlyはスキップ
|
||||
if prop.is_readonly:
|
||||
print(f"[VoiceVox Debug] スキップ(readonly): {prop_name}")
|
||||
pass
|
||||
else:
|
||||
settings[prop_name] = value
|
||||
print(f"[VoiceVox] コピー: {prop_name} = {value}")
|
||||
except Exception as e:
|
||||
print(f"[VoiceVox] {prop_name} の取得をスキップ: {e}")
|
||||
|
||||
print(f"[VoiceVox] テキストストリップ '{text_strip.name}' から {len(settings)} 個のプロパティを抽出")
|
||||
|
||||
return settings
|
||||
|
||||
|
||||
|
|
@ -151,16 +126,10 @@ class VOICEVOX_OT_generate_speech(Operator):
|
|||
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}")
|
||||
|
||||
# speakerプロパティからIDを取得
|
||||
speaker_id = int(props.speaker) if props.speaker else 0
|
||||
|
||||
audio_file = api.generate_speech(
|
||||
|
|
@ -176,29 +145,22 @@ class VOICEVOX_OT_generate_speech(Operator):
|
|||
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()
|
||||
|
||||
|
|
@ -206,7 +168,6 @@ class VOICEVOX_OT_generate_speech(Operator):
|
|||
frame_current = context.scene.frame_current
|
||||
props = context.scene.voicevox
|
||||
|
||||
# 音声を追加
|
||||
audio_strip = seq_editor.strips.new_sound(
|
||||
name="VoiceVox Audio",
|
||||
filepath=audio_file,
|
||||
|
|
@ -214,26 +175,19 @@ class VOICEVOX_OT_generate_speech(Operator):
|
|||
frame_start=frame_current,
|
||||
)
|
||||
|
||||
# 音声の長さを取得(フレーム数)
|
||||
audio_duration_frames = audio_strip.frame_final_duration
|
||||
print(f"[VoiceVox] 音声の長さ: {audio_duration_frames} フレーム")
|
||||
|
||||
# 選択されているテキストストリップから設定を抽出(あれば)
|
||||
text_settings = get_text_strip_settings(context)
|
||||
|
||||
# 字幕を追加(音声の長さに合わせる)
|
||||
subtitle_mgr = SubtitleManager()
|
||||
|
||||
# テキストストリップから設定を取得できた場合はそれを優先
|
||||
if text_settings:
|
||||
font_size = text_settings.get('font_size', 48) # デフォルト48
|
||||
position_y = text_settings.get('position_y', 0.1) # デフォルト0.1
|
||||
channel = text_settings.get('channel', props.subtitle_channel)
|
||||
# その他の設定も渡す
|
||||
extra_settings = {k: v for k, v in text_settings.items()
|
||||
if k not in ['font_size', 'position_y', 'channel']}
|
||||
else:
|
||||
# リファレンスがない場合のデフォルト値
|
||||
font_size = 48
|
||||
position_y = 0.1
|
||||
channel = props.subtitle_channel
|
||||
|
|
@ -243,7 +197,7 @@ class VOICEVOX_OT_generate_speech(Operator):
|
|||
context=context,
|
||||
text=text,
|
||||
frame_start=frame_current,
|
||||
duration_frames=audio_duration_frames, # 音声の長さに合わせる
|
||||
duration_frames=audio_duration_frames,
|
||||
font_size=font_size,
|
||||
position_y=position_y,
|
||||
channel=channel,
|
||||
|
|
@ -270,25 +224,20 @@ class VOICEVOX_OT_add_subtitle(Operator):
|
|||
|
||||
frame_current = context.scene.frame_current
|
||||
|
||||
# 字幕のみの場合はデフォルト3秒
|
||||
default_duration_sec = 3.0
|
||||
duration_frames = int(default_duration_sec * context.scene.render.fps)
|
||||
|
||||
# 選択されているテキストストリップから設定を抽出(あれば)
|
||||
text_settings = get_text_strip_settings(context)
|
||||
|
||||
subtitle_mgr = SubtitleManager()
|
||||
|
||||
# テキストストリップから設定を取得できた場合はそれを優先
|
||||
if text_settings:
|
||||
font_size = text_settings.get('font_size', 48) # デフォルト48
|
||||
position_y = text_settings.get('position_y', 0.1) # デフォルト0.1
|
||||
channel = text_settings.get('channel', props.subtitle_channel)
|
||||
# その他の設定も渡す
|
||||
extra_settings = {k: v for k, v in text_settings.items()
|
||||
if k not in ['font_size', 'position_y', 'channel']}
|
||||
else:
|
||||
# リファレンスがない場合のデフォルト値
|
||||
font_size = 48
|
||||
position_y = 0.1
|
||||
channel = props.subtitle_channel
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ class VOICEVOX_OT_set_reference_text(Operator):
|
|||
seq_editor = context.scene.sequence_editor
|
||||
props = context.scene.voicevox
|
||||
|
||||
# 選択されているテキストストリップを探す
|
||||
text_strip = None
|
||||
|
||||
if seq_editor.active_strip and seq_editor.active_strip.type == 'TEXT':
|
||||
|
|
@ -36,10 +35,8 @@ class VOICEVOX_OT_set_reference_text(Operator):
|
|||
self.report({'ERROR'}, "テキストストリップが選択されていません")
|
||||
return {'CANCELLED'}
|
||||
|
||||
# リファレンスとして設定
|
||||
props.reference_text_strip = text_strip.name
|
||||
self.report({'INFO'}, f"リファレンステキスト: '{text_strip.name}'")
|
||||
print(f"[VoiceVox] リファレンステキストを設定: {text_strip.name}")
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
|
@ -55,6 +52,5 @@ class VOICEVOX_OT_clear_reference_text(Operator):
|
|||
props = context.scene.voicevox
|
||||
props.reference_text_strip = ""
|
||||
self.report({'INFO'}, "リファレンステキストをクリアしました")
|
||||
print(f"[VoiceVox] リファレンステキストをクリア")
|
||||
|
||||
return {'FINISHED'}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ from bpy.props import (
|
|||
EnumProperty,
|
||||
)
|
||||
|
||||
# グローバル変数で話者リストをキャッシュ
|
||||
_speaker_cache = []
|
||||
|
||||
|
||||
|
|
@ -21,7 +20,6 @@ def get_speaker_items(self, context):
|
|||
global _speaker_cache
|
||||
|
||||
if not _speaker_cache:
|
||||
# キャッシュが空の場合はデフォルトを返す
|
||||
return [('0', 'リロードボタンを押してください', 'VoiceVoxから話者リストを取得')]
|
||||
|
||||
return _speaker_cache
|
||||
|
|
@ -47,7 +45,6 @@ def update_speaker_cache(host: str = "127.0.0.1", port: int = 50021):
|
|||
style_name = style.get('name', '')
|
||||
style_id = style.get('id', 0)
|
||||
|
||||
# 表示名: "キャラクター名 (スタイル名)"
|
||||
display_name = f"{name} ({style_name})" if style_name else name
|
||||
|
||||
items.append((
|
||||
|
|
@ -64,8 +61,6 @@ def update_speaker_cache(host: str = "127.0.0.1", port: int = 50021):
|
|||
|
||||
except Exception as e:
|
||||
print(f"[VoiceVox] 話者一覧の取得に失敗: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
_speaker_cache = [('0', '接続エラー', str(e))]
|
||||
return 0
|
||||
|
||||
|
|
@ -73,7 +68,6 @@ def update_speaker_cache(host: str = "127.0.0.1", port: int = 50021):
|
|||
class VoiceVoxProperties(bpy.types.PropertyGroup):
|
||||
"""VoiceVoxプラグインのプロパティ"""
|
||||
|
||||
# VoiceVox エンジンの設定
|
||||
voicevox_host: StringProperty(
|
||||
name="VoiceVox Host",
|
||||
description="VoiceVoxエンジンのホストアドレス",
|
||||
|
|
@ -88,7 +82,6 @@ class VoiceVoxProperties(bpy.types.PropertyGroup):
|
|||
max=65535,
|
||||
)
|
||||
|
||||
# 音声合成の設定
|
||||
text: StringProperty(
|
||||
name="Text",
|
||||
description="音声合成するテキスト",
|
||||
|
|
@ -101,7 +94,6 @@ class VoiceVoxProperties(bpy.types.PropertyGroup):
|
|||
items=get_speaker_items,
|
||||
)
|
||||
|
||||
# 後方互換性のため残す(内部的にはspeakerを使用)
|
||||
speaker_id: IntProperty(
|
||||
name="Speaker ID",
|
||||
description="話者ID (VoiceVoxのキャラクター)",
|
||||
|
|
@ -141,7 +133,6 @@ class VoiceVoxProperties(bpy.types.PropertyGroup):
|
|||
max=2.0,
|
||||
)
|
||||
|
||||
# チャンネル設定
|
||||
audio_channel: IntProperty(
|
||||
name="Audio Channel",
|
||||
description="音声を配置するチャンネル番号",
|
||||
|
|
@ -158,14 +149,12 @@ class VoiceVoxProperties(bpy.types.PropertyGroup):
|
|||
max=128,
|
||||
)
|
||||
|
||||
# リファレンステキストストリップ
|
||||
reference_text_strip: StringProperty(
|
||||
name="Reference Text Strip",
|
||||
description="設定を流用するリファレンステキストストリップの名前",
|
||||
default="",
|
||||
)
|
||||
|
||||
# 出力設定
|
||||
output_directory: StringProperty(
|
||||
name="Output Directory",
|
||||
description="音声ファイルの出力先ディレクトリ",
|
||||
|
|
|
|||
16
subtitles.py
16
subtitles.py
|
|
@ -39,7 +39,6 @@ class SubtitleManager:
|
|||
|
||||
seq_editor = scene.sequence_editor
|
||||
|
||||
# テキストストリップを追加
|
||||
text_strip = seq_editor.strips.new_effect(
|
||||
name="Subtitle",
|
||||
type='TEXT',
|
||||
|
|
@ -48,10 +47,8 @@ class SubtitleManager:
|
|||
length=duration_frames,
|
||||
)
|
||||
|
||||
# テキスト内容を設定
|
||||
text_strip.text = text
|
||||
|
||||
# デフォルト設定
|
||||
default_settings = {
|
||||
'use_shadow': True,
|
||||
'shadow_color': (0.0, 0.0, 0.0, 0.8),
|
||||
|
|
@ -61,61 +58,48 @@ class SubtitleManager:
|
|||
'align_y': 'BOTTOM',
|
||||
}
|
||||
|
||||
# デフォルト設定から開始し、extra_settingsで上書き
|
||||
all_settings = default_settings.copy()
|
||||
if extra_settings:
|
||||
all_settings.update(extra_settings)
|
||||
|
||||
# font_sizeは常にパラメータの値を使用
|
||||
all_settings['font_size'] = font_size
|
||||
|
||||
# 位置の処理
|
||||
position_x = all_settings.pop('position_x', 0.5)
|
||||
position_y_val = all_settings.pop('position_y', position_y)
|
||||
|
||||
# Transform関連のプロパティを分離
|
||||
transform_settings = {}
|
||||
other_settings = {}
|
||||
|
||||
for prop, value in all_settings.items():
|
||||
if prop.startswith('transform_'):
|
||||
# transform_offset_x → offset_x
|
||||
transform_prop_name = prop[len('transform_'):]
|
||||
transform_settings[transform_prop_name] = value
|
||||
else:
|
||||
other_settings[prop] = value
|
||||
|
||||
# 通常のプロパティを設定
|
||||
for prop, value in other_settings.items():
|
||||
try:
|
||||
setattr(text_strip, prop, value)
|
||||
print(f"[Subtitle] 設定: {prop} = {value}")
|
||||
except AttributeError:
|
||||
print(f"[Subtitle] {prop} プロパティは利用できません(スキップ)")
|
||||
except Exception as e:
|
||||
print(f"[Subtitle] {prop} の設定に失敗: {e}")
|
||||
|
||||
# 位置を設定
|
||||
try:
|
||||
text_strip.location[0] = position_x
|
||||
text_strip.location[1] = position_y_val
|
||||
print(f"[Subtitle] location = ({position_x}, {position_y_val})")
|
||||
except (AttributeError, TypeError) as e:
|
||||
print(f"[Subtitle] location プロパティは利用できません: {e}")
|
||||
|
||||
# Transformプロパティを設定
|
||||
if transform_settings and hasattr(text_strip, 'transform'):
|
||||
for prop, value in transform_settings.items():
|
||||
try:
|
||||
setattr(text_strip.transform, prop, value)
|
||||
print(f"[Subtitle] 設定: transform.{prop} = {value}")
|
||||
except AttributeError:
|
||||
print(f"[Subtitle] transform.{prop} プロパティは利用できません(スキップ)")
|
||||
except Exception as e:
|
||||
print(f"[Subtitle] transform.{prop} の設定に失敗: {e}")
|
||||
|
||||
print(f"Subtitle added: '{text}' at frame {frame_start}")
|
||||
|
||||
return text_strip
|
||||
|
||||
def update_subtitle(self, text_strip, **kwargs):
|
||||
|
|
|
|||
33
voicevox.py
33
voicevox.py
|
|
@ -25,7 +25,7 @@ class VoiceVoxAPI:
|
|||
with urllib.request.urlopen(url, timeout=5) as response:
|
||||
return response.status == 200
|
||||
except Exception as e:
|
||||
print(f"Connection test failed: {e}")
|
||||
print(f"[VoiceVox API Error] Connection test failed: {e}")
|
||||
return False
|
||||
|
||||
def get_speakers(self) -> Optional[list]:
|
||||
|
|
@ -34,16 +34,11 @@ class VoiceVoxAPI:
|
|||
url = f"{self.base_url}/speakers"
|
||||
with urllib.request.urlopen(url, timeout=10) as response:
|
||||
data = response.read()
|
||||
# UTF-8として明示的にデコード
|
||||
text = data.decode("utf-8")
|
||||
speakers = json.loads(text)
|
||||
print(f"[VoiceVox API] 話者を {len(speakers)} 人取得")
|
||||
return speakers
|
||||
except Exception as e:
|
||||
print(f"Failed to get speakers: {e}")
|
||||
import traceback
|
||||
|
||||
traceback.print_exc()
|
||||
print(f"[VoiceVox API Error] Failed to get speakers: {e}")
|
||||
return None
|
||||
|
||||
def generate_speech(
|
||||
|
|
@ -63,11 +58,8 @@ class VoiceVoxAPI:
|
|||
生成された音声ファイルのパス、失敗時はNone
|
||||
"""
|
||||
try:
|
||||
print(f"[VoiceVox API] Step 1: 出力ディレクトリを作成: {output_dir}")
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
|
||||
# 1. 音声合成用のクエリを作成
|
||||
print(f"[VoiceVox API] Step 2: 音声クエリを生成中...")
|
||||
query_url = f"{self.base_url}/audio_query"
|
||||
query_params = urllib.parse.urlencode(
|
||||
{
|
||||
|
|
@ -77,14 +69,11 @@ class VoiceVoxAPI:
|
|||
)
|
||||
|
||||
full_query_url = f"{query_url}?{query_params}"
|
||||
print(f"[VoiceVox API] リクエストURL: {full_query_url}")
|
||||
|
||||
try:
|
||||
# POSTリクエストにするため空のdataを渡す
|
||||
req = urllib.request.Request(full_query_url, data=b"", method="POST")
|
||||
with urllib.request.urlopen(req, timeout=10) as response:
|
||||
query_data = json.loads(response.read())
|
||||
print(f"[VoiceVox API] クエリ生成成功")
|
||||
except urllib.error.HTTPError as e:
|
||||
print(f"[VoiceVox API Error] HTTPエラー: {e.code} {e.reason}")
|
||||
print(
|
||||
|
|
@ -96,7 +85,7 @@ class VoiceVoxAPI:
|
|||
print(
|
||||
f"[VoiceVox API Error] VoiceVoxエンジンが起動しているか確認してください"
|
||||
)
|
||||
raisee
|
||||
raise
|
||||
|
||||
# パラメータを適用
|
||||
query_data["speedScale"] = speed_scale
|
||||
|
|
@ -104,8 +93,6 @@ class VoiceVoxAPI:
|
|||
query_data["intonationScale"] = intonation_scale
|
||||
query_data["volumeScale"] = volume_scale
|
||||
|
||||
# 2. 音声合成を実行
|
||||
print(f"[VoiceVox API] Step 3: 音声合成を実行中...")
|
||||
synthesis_url = f"{self.base_url}/synthesis"
|
||||
synthesis_params = urllib.parse.urlencode({"speaker": speaker_id})
|
||||
|
||||
|
|
@ -119,7 +106,6 @@ class VoiceVoxAPI:
|
|||
try:
|
||||
with urllib.request.urlopen(req, timeout=30) as response:
|
||||
audio_data = response.read()
|
||||
print(f"[VoiceVox API] 音声合成成功 ({len(audio_data)} bytes)")
|
||||
except urllib.error.HTTPError as e:
|
||||
print(f"[VoiceVox API Error] 合成HTTPエラー: {e.code} {e.reason}")
|
||||
print(
|
||||
|
|
@ -127,8 +113,6 @@ class VoiceVoxAPI:
|
|||
)
|
||||
raise
|
||||
|
||||
# 3. 音声ファイルを保存
|
||||
print(f"[VoiceVox API] Step 4: 音声ファイルを保存中...")
|
||||
text_hash = hashlib.md5(text.encode("utf-8")).hexdigest()[:8]
|
||||
filename = f"voicevox_{text_hash}_{speaker_id}.wav"
|
||||
filepath = os.path.join(output_dir, filename)
|
||||
|
|
@ -136,7 +120,6 @@ class VoiceVoxAPI:
|
|||
with open(filepath, "wb") as f:
|
||||
f.write(audio_data)
|
||||
|
||||
print(f"[VoiceVox API] 音声保存完了: {filepath}")
|
||||
return filepath
|
||||
|
||||
except urllib.error.URLError as e:
|
||||
|
|
@ -144,25 +127,15 @@ class VoiceVoxAPI:
|
|||
f"接続エラー: VoiceVoxエンジンに接続できません ({self.base_url})"
|
||||
)
|
||||
print(f"[VoiceVox API Error] {error_msg}")
|
||||
print(f"[VoiceVox API Error] 詳細: {e}")
|
||||
self.last_error = error_msg
|
||||
import traceback
|
||||
|
||||
traceback.print_exc()
|
||||
return None
|
||||
except urllib.error.HTTPError as e:
|
||||
error_msg = f"HTTPエラー {e.code}: {e.reason}"
|
||||
print(f"[VoiceVox API Error] {error_msg}")
|
||||
self.last_error = error_msg
|
||||
import traceback
|
||||
|
||||
traceback.print_exc()
|
||||
return None
|
||||
except Exception as e:
|
||||
error_msg = f"{type(e).__name__}: {str(e)}"
|
||||
print(f"[VoiceVox API Error] 音声生成失敗: {error_msg}")
|
||||
self.last_error = error_msg
|
||||
import traceback
|
||||
|
||||
traceback.print_exc()
|
||||
return None
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user