nekoniii3 commited on
Commit
0d45fdb
1 Parent(s): 2b03442
Files changed (2) hide show
  1. app.py +355 -0
  2. requirements.txt +3 -0
app.py ADDED
@@ -0,0 +1,355 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import gradio as gr
3
+ from openai import OpenAI
4
+ from yt_dlp import YoutubeDL
5
+ from pydub import AudioSegment
6
+ import math
7
+
8
+ # 定数
9
+ MAX_LENGTH = 5000
10
+ MODEL_SUMMARY = "gpt-3.5-turbo-0125"
11
+
12
+ # 分割時間設定(20分)
13
+ split_time = 20 * 60 * 1000
14
+
15
+ lang_code = {'Japanese': "ja", 'English': "en"}
16
+
17
+ def set_state(openai_key, lang, state):
18
+ """ 設定タブの情報をセッションに保存する関数 """
19
+
20
+ state["openai_key"]= openai_key
21
+ state["lang"] = lang_code[lang]
22
+
23
+ return state
24
+
25
+ def youtube_mp3(url):
26
+
27
+ # ファイル名は動画のIDに
28
+ mp3_name = url[url.rfind('/') + 1:].replace("watch?v=","")
29
+
30
+
31
+ # フォーマット設定
32
+ ydl_opts = {
33
+ 'outtmpl': mp3_name,
34
+ "format": "mp3/bestaudio/best",
35
+ "postprocessors": [
36
+ {
37
+ "key": "FFmpegExtractAudio",
38
+ "preferredcodec": "mp3",
39
+ }
40
+ ],
41
+ }
42
+
43
+ try :
44
+ # yt-dlpでmp3に
45
+ with YoutubeDL(ydl_opts) as ydl:
46
+ result = ydl.download([url])
47
+
48
+ return mp3_name + ".mp3"
49
+
50
+ except Exception as e:
51
+ print(e)
52
+ return None
53
+
54
+
55
+ def create_textfile(url, file, state):
56
+
57
+ err_msg = ""
58
+
59
+ # OpenAIキーチェック
60
+ if state["openai_key"] == "":
61
+
62
+ err_msg = "OpenAIキーを入力してください。(設定タブ)"
63
+
64
+ return None, err_msg
65
+
66
+ # URL入力チェック
67
+ if url == "" and file is None:
68
+
69
+ err_msg = "URLを入力するか、音声ファイルをアップして下さい。"
70
+
71
+ return None, err_msg
72
+
73
+ # OpenAIクライアント作成
74
+ os.environ["OPENAI_API_KEY"] = state["openai_key"]
75
+
76
+ client = OpenAI()
77
+
78
+ # client作成後は消す
79
+ os.environ["OPENAI_API_KEY"] = ""
80
+
81
+ # 動画を音声ファイルにする
82
+ if url != "":
83
+
84
+ file_name = youtube_mp3(url)
85
+
86
+ if file_name is None:
87
+
88
+ err_msg = "音声ファイルにする作業でエラーが発生しました。URLを確認して下さい。"
89
+
90
+ return None, err_msg
91
+
92
+ else:
93
+ file_name = file
94
+
95
+ # 音声ファイルを分割
96
+ audio_list = audio_seg(file_name)
97
+
98
+ # whisperで文字に起こす
99
+ text_list = whisper_audio(client, audio_list, state["lang"])
100
+
101
+ if text_list is None:
102
+
103
+ err_msg = "whisperでエラーが発生しました。OpenAI APIキーが正しいか、クレジット残高があるか確認して下さい。"
104
+
105
+ return None, err_msg
106
+
107
+
108
+ # テキストファイルを結合
109
+ text_name = file_name.replace(".mp3",".txt")
110
+
111
+ combin_text(text_list, text_name)
112
+
113
+ return text_name, err_msg
114
+
115
+
116
+ def whisper_audio(client, audio_list, lang):
117
+
118
+ text_list = []
119
+
120
+ try:
121
+
122
+ for audio in audio_list:
123
+
124
+ audio_file= open(audio, "rb")
125
+
126
+ trans_text = client.audio.transcriptions.create(model="whisper-1", file=audio_file, language=lang, response_format="text")
127
+
128
+ # ファイル名設定
129
+ text_name = audio.replace(".mp3","_.txt")
130
+
131
+ with open(text_name, mode="w") as f:
132
+
133
+ # テキストに書き出す
134
+ f.write(trans_text)
135
+
136
+ text_list.append(text_name)
137
+
138
+ return text_list
139
+
140
+ except Exception as e:
141
+
142
+ return None
143
+
144
+
145
+ def audio_seg(file_name):
146
+
147
+ # 分割したリスト
148
+ div_file = []
149
+
150
+ # ファイルから音声情報取得
151
+ audio = AudioSegment.from_mp3(file_name)
152
+
153
+ # 音声の長さ取得(ミリ秒)
154
+ dur_mlseconds = audio.duration_seconds * 1000
155
+
156
+ # 分割数を決める
157
+ div_count = math.ceil(dur_mlseconds / split_time)
158
+
159
+ if div_count == 1:
160
+
161
+ # 分割なしの時は元ファイルのみ
162
+ div_file.append(file_name)
163
+
164
+ return div_file
165
+
166
+ # 分割時間初期設定
167
+ start = 0
168
+ end = split_time
169
+
170
+ for i in range(div_count):
171
+
172
+ div_audio = audio[start:end]
173
+ div_audio.export(str(i+1) + "_" + file_name, format="mp3")
174
+
175
+ # ファイル名をセット
176
+ div_file.append(str(i+1) + "_" + file_name)
177
+
178
+ start = end
179
+ end += split_time
180
+
181
+ return div_file
182
+
183
+
184
+ def combin_text(text_list, text_name):
185
+
186
+ # ファイルを一つにまとめる
187
+ with open(text_name, "w") as of:
188
+ for file in text_list:
189
+ with open(file, "r") as f:
190
+ of.write(f.read())
191
+
192
+ # 必要であれば改行を加える
193
+ # outfile.write(infile.read() + "\n")
194
+
195
+ return
196
+
197
+ def create_mp3(url, state):
198
+
199
+ err_msg = ""
200
+
201
+ # URL入力チェック
202
+ if url == "":
203
+
204
+ err_msg = "URLを入力して下さい。"
205
+
206
+ return None, err_msg
207
+
208
+ # 動画を音声ファイルにする
209
+ file_name = youtube_mp3(url)
210
+
211
+ if file_name is None:
212
+
213
+ err_msg = "音声ファイルにする作業でエラーが発生しました。URLを確認して下さい。"
214
+
215
+ return None, err_msg
216
+
217
+ return file_name, ""
218
+
219
+ def create_summary(file, state):
220
+
221
+ print(file)
222
+
223
+ err_msg = ""
224
+
225
+ # OpenAIキーチェック
226
+ if state["openai_key"] == "":
227
+
228
+ err_msg = "OpenAIキーを入力してください。(設定タブ)"
229
+
230
+ return None, err_msg
231
+
232
+ # URL入力チェック
233
+ if file is None:
234
+
235
+ err_msg = "テキストがありません。"
236
+
237
+ return None, err_msg
238
+
239
+ # OpenAIクライアント作成
240
+ os.environ["OPENAI_API_KEY"] = state["openai_key"]
241
+
242
+ client = OpenAI()
243
+
244
+ # client作成後は消す
245
+ os.environ["OPENAI_API_KEY"] = ""
246
+
247
+ summary, err_msg = exec_summary(client, file)
248
+
249
+ if err_msg != "":
250
+
251
+ return file, err_msg
252
+
253
+ sum_file = "summary_" + os.path.basename(file)
254
+
255
+ with open(sum_file, mode="w") as f:
256
+
257
+ f.write(summary)
258
+
259
+ return sum_file, ""
260
+
261
+
262
+ def exec_summary(client, file):
263
+
264
+ try:
265
+
266
+ with open(file, 'r') as f:
267
+
268
+ text = f.read()
269
+
270
+ if len(text) > MAX_LENGTH:
271
+
272
+ err_msg = "要約の文字数上限を超えています。"
273
+
274
+ return "", err_msg
275
+
276
+ messages=[
277
+ {"role": "system", "content": "あなたは優秀なアシスタントです。与えられた文章を要約して下さい。"},
278
+ {"role": "user", "content": text},
279
+ ]
280
+
281
+ # GPTに問い合わせ
282
+ response = client.chat.completions.create(
283
+ model=MODEL_SUMMARY,
284
+ messages=messages,
285
+ # max_tokens=MAX_TOKENS,
286
+ )
287
+
288
+ summary = response.choices[0].message.content
289
+
290
+ return summary, ""
291
+
292
+ except Exception as e:
293
+ print(e)
294
+ return "", "要約作成でエラーが発生しました。"
295
+
296
+
297
+ with gr.Blocks() as demo:
298
+
299
+ title = "<h2>Whisperデモアプリ【応用版】</h2>"
300
+ message = "<h3>最初に[設定]タブからOpenAIキーを入力してください。"
301
+ message += "</h3>"
302
+
303
+ gr.Markdown(title + message)
304
+
305
+ # セッションの宣言
306
+ state = gr.State({
307
+ "openai_key" : "",
308
+ "lang": ""
309
+ })
310
+
311
+ with gr.Tab("whisperを利用する") as main:
312
+
313
+ # 各コンポーネント定義
314
+ url = gr.Text(label="YouTubeのURL")
315
+
316
+ with gr.Accordion(label="音声ファイルをアップする", open=False):
317
+ up_file = gr.File(file_types=[".mp3", ".mp4", ".mpeg", ".mpga", ".m4a", ".wav", ".webm"], label="音声ファイルアップロード")
318
+
319
+ # ボタン類
320
+ with gr.Row():
321
+ btn_txt = gr.Button("テキスト作成")
322
+ btn_mp3 = gr.Button("MP3作成")
323
+ # clear = gr.ClearButton(value="リセット", components=[url, sys_msg])
324
+
325
+ # 出力
326
+ sys_msg = gr.Text(label="システムメッセージ")
327
+ # text = gr.TextArea(label="文字起こし内容")
328
+ out_file = gr.File(label="出力テキストファイル", interactive = False, value="DmmWa-Fnxms.txt")
329
+
330
+ with gr.Accordion(label="テキストを要約する", open=False):
331
+ btn_sum = gr.Button("テキスト要約")
332
+ sum_file = gr.File(label="要約テキストファイル", interactive = False)
333
+
334
+ # 送信ボタンクリック時の処理
335
+ btn_txt.click(create_textfile, inputs=[url, up_file, state], outputs=[out_file, sys_msg], queue=False)
336
+ btn_mp3.click(create_mp3, inputs=[url, state], outputs=[out_file, sys_msg], queue=False)
337
+ btn_sum.click(create_summary, inputs=[out_file, state], outputs=[sum_file, sys_msg], queue=False)
338
+
339
+ with gr.Tab("設定") as set:
340
+ openai_key = gr.Textbox(label="OpenAI API Key", interactive = True)
341
+ lang = gr.Dropdown(choices=["Japanese", "English"], value = "Japanese", label="Language", interactive = True)
342
+
343
+ # 設定変更時
344
+ main.select(set_state, [openai_key, lang, state], state)
345
+
346
+ with gr.Tab("利用上の注意"):
347
+
348
+ caution = '・URLとファイルが両方ある場合はURLが優先されます。<br>'
349
+ caution += "・文字起こしにはお金がかかりますが(1分あたり約0.9円)、MP3作成のみは無料です。<br>"
350
+ caution += "・要約の際のGPTのモデルはGPT3.5です(gpt-3.5-turbo-0125)<br>"
351
+ caution += "・要約には料金が発生します。(1000文字あたり約0.2円)<br>"
352
+ gr.Markdown("<h3>" + caution + "</h3>")
353
+
354
+ demo.queue()
355
+ demo.launch(debug=False)
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ # gradio==4.19.0
2
+ openai==1.12.0
3
+ yt-dlp==2023.12.30