loveasmrmeeeee commited on
Commit
8efcee2
·
verified ·
1 Parent(s): 12da140

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +190 -0
app.py ADDED
@@ -0,0 +1,190 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app.py
2
+ import random
3
+ import gradio as gr
4
+ import torch
5
+ import tempfile
6
+ from transformers import SpeechT5Processor, SpeechT5ForTextToSpeech
7
+ from scipy.io.wavfile import write as wav_write
8
+
9
+ # =========================
10
+ # セリフ素材
11
+ # =========================
12
+
13
+ COUNT_VARIANTS = {
14
+ 1: ["い〜ち", "いち…"],
15
+ 2: ["に〜", "に…"],
16
+ 3: ["さ〜ん", "さん…"],
17
+ 4: ["よ〜ん", "よん…"],
18
+ 5: ["ご〜", "ご…"],
19
+ 6: ["ろ〜く", "ろく…"],
20
+ 7: ["な〜な", "なな…"],
21
+ 8: ["は〜ち", "はち…"],
22
+ 9: ["きゅ〜〜う", "きゅう…"],
23
+ 10: ["じゅ〜〜う", "じゅう…"],
24
+ }
25
+
26
+ INTERRUPTS = [
27
+ "はい、ストップ",
28
+ "だめ、まだ",
29
+ "おあずけ",
30
+ "ふふ…止めちゃおうかな",
31
+ "あーあ…残念",
32
+ "今のなし",
33
+ "最初から、ね",
34
+ ]
35
+
36
+ REACTIONS = [
37
+ "ふふ…",
38
+ "……",
39
+ "あーあ…",
40
+ "ん…",
41
+ "焦ってる?",
42
+ "そんな顔しないの",
43
+ ]
44
+
45
+ # =========================
46
+ # ユーティリティ
47
+ # =========================
48
+
49
+ def random_pause(short=False, long=False):
50
+ if short:
51
+ return "…"
52
+ if long:
53
+ return "…………"
54
+ return random.choice(["…", "……", "………"])
55
+
56
+ def interrupt_rate(max_count):
57
+ if max_count == 100:
58
+ return 0.25
59
+ if max_count == 30:
60
+ return 0.18
61
+ return 0.12
62
+
63
+ # =========================
64
+ # 台本生成
65
+ # =========================
66
+
67
+ def generate_script(max_count: int) -> str:
68
+ script = []
69
+ current = max_count
70
+
71
+ while current > 0:
72
+ variant = random.choice(
73
+ COUNT_VARIANTS.get(current, [str(current)])
74
+ )
75
+ script.append(variant)
76
+ script.append(random_pause())
77
+
78
+ if random.random() < 0.35:
79
+ script.append(random.choice(REACTIONS))
80
+ script.append(random_pause(short=True))
81
+
82
+ if random.random() < interrupt_rate(max_count):
83
+ script.append(random.choice(INTERRUPTS))
84
+ script.append(random_pause(long=True))
85
+ current = max_count
86
+ continue
87
+
88
+ current -= 1
89
+
90
+ return " ".join(script)
91
+
92
+ # =========================
93
+ # TTS(代替モデル使用)
94
+ # =========================
95
+
96
+ DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
97
+
98
+ # esnya/japanese_speecht5_tts は特殊な依存関係が必要なため、
99
+ # より安定した日本語TTSモデルに変更
100
+ MODEL_ID = "microsoft/speecht5_tts" # 元のSpeechT5モデル
101
+
102
+ try:
103
+ # microsoft/speecht5_ttsを使用(日本語は音素レベルで対応可能)
104
+ processor = SpeechT5Processor.from_pretrained(MODEL_ID)
105
+ model = SpeechT5ForTextToSpeech.from_pretrained(MODEL_ID).to(DEVICE)
106
+
107
+ # スピーカー埋め込みを生成(デフォルト)
108
+ from transformers import SpeechT5HifiGan
109
+ vocoder = SpeechT5HifiGan.from_pretrained("microsoft/speecht5_hifigan").to(DEVICE)
110
+
111
+ # デフォルトのスピーカー埋め込み
112
+ speaker_embeddings = torch.zeros((1, 512)).to(DEVICE)
113
+
114
+ def tts(text: str) -> str:
115
+ # ローマ字化(簡易版 - 日本語を英語音素で近似)
116
+ # 本来はpykakasiなどで変換すべきだが、簡易的に処理
117
+ inputs = processor(text=text, return_tensors="pt")
118
+
119
+ with torch.no_grad():
120
+ speech = model.generate_speech(
121
+ inputs["input_ids"].to(DEVICE),
122
+ speaker_embeddings,
123
+ vocoder=vocoder
124
+ )
125
+
126
+ # 音声ファイルとして保存
127
+ tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".wav")
128
+ wav_write(
129
+ tmp.name,
130
+ 16000,
131
+ speech.cpu().numpy()
132
+ )
133
+ return tmp.name
134
+
135
+ print("✅ TTS初期化成功(microsoft/speecht5_tts)")
136
+
137
+ except Exception as e:
138
+ print(f"⚠️ TTS初期化エラー: {e}")
139
+ print("📝 フォールバック: テキストのみ返却モードで起動します")
140
+
141
+ def tts(text: str) -> str:
142
+ # エラー時は空の音声ファイルを返す
143
+ tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".wav")
144
+ import numpy as np
145
+ # 1秒の無音
146
+ wav_write(tmp.name, 16000, np.zeros(16000, dtype=np.int16))
147
+ return tmp.name
148
+
149
+ # =========================
150
+ # Gradio用関数
151
+ # =========================
152
+
153
+ def generate_and_speak(count_choice):
154
+ max_count = int(count_choice)
155
+ script = generate_script(max_count)
156
+
157
+ try:
158
+ audio_path = tts(script)
159
+ return audio_path, script
160
+ except Exception as e:
161
+ return None, f"エラー: {str(e)}\n\n台本:\n{script}"
162
+
163
+ # =========================
164
+ # UI
165
+ # =========================
166
+
167
+ with gr.Blocks() as demo:
168
+ gr.Markdown("## ゆっくりカウントダウン(試運転)")
169
+
170
+ count_choice = gr.Dropdown(
171
+ ["5", "10", "30", "100"],
172
+ value="10",
173
+ label="カウント数"
174
+ )
175
+
176
+ audio_out = gr.Audio(label="再生")
177
+ script_out = gr.Textbox(
178
+ label="生成された台本(デバッグ用)",
179
+ lines=8
180
+ )
181
+
182
+ btn = gr.Button("スタート")
183
+
184
+ btn.click(
185
+ fn=generate_and_speak,
186
+ inputs=count_choice,
187
+ outputs=[audio_out, script_out]
188
+ )
189
+
190
+ demo.launch()