Johnny0619 commited on
Commit
3edbc8d
·
verified ·
1 Parent(s): 132c2e0

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +250 -0
app.py ADDED
@@ -0,0 +1,250 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import re
3
+ import time
4
+ import json
5
+ import tempfile
6
+ import gradio as gr
7
+ from pptx import Presentation
8
+ from pptx.util import Pt, Inches, Emu
9
+ from pptx.enum.text import PP_ALIGN, MSO_ANCHOR
10
+ from pptx.oxml.xmlchemy import OxmlElement
11
+ import google.generativeai as genai
12
+ from google.cloud import texttospeech
13
+ from google.cloud import storage
14
+ from google.oauth2 import service_account
15
+
16
+ # ===================================================================
17
+ # 1. 認証・環境設定
18
+ # ===================================================================
19
+
20
+ # 環境変数から設定を読み込む
21
+ GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY")
22
+ GCP_JSON_CONTENT = os.environ.get("GCP_JSON")
23
+ GCS_BUCKET_NAME = os.environ.get("GCS_BUCKET_NAME")
24
+
25
+ # Geminiのセットアップ
26
+ if GEMINI_API_KEY:
27
+ genai.configure(api_key=GEMINI_API_KEY)
28
+ gemini_model = genai.GenerativeModel('gemini-1.5-pro') # 安定版推奨
29
+ else:
30
+ print("Warning: GEMINI_API_KEY is not set.")
31
+
32
+ # GCP認証情報のセットアップ(JSON文字列を一時ファイルに書き出して使用)
33
+ def setup_gcp_credentials():
34
+ if not GCP_JSON_CONTENT:
35
+ return None
36
+ try:
37
+ # 一時ファイルを作成
38
+ with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.json') as f:
39
+ f.write(GCP_JSON_CONTENT)
40
+ return f.name
41
+ except Exception as e:
42
+ print(f"Error setting up GCP credentials: {e}")
43
+ return None
44
+
45
+ GCP_CREDENTIALS_PATH = setup_gcp_credentials()
46
+ if GCP_CREDENTIALS_PATH:
47
+ os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = GCP_CREDENTIALS_PATH
48
+
49
+ # フォント等の設定(元のコードから継承)
50
+ FONT_SETTINGS = {
51
+ 'FONT_FAMILY': '游ゴシック', # クラウド上では代替フォントになる可能性があります
52
+ 'CONTENT_PAGE_MAIN_TITLE_SIZE': Pt(20),
53
+ 'CONTENT_PAGE_SUB_TITLE_SIZE': Pt(12),
54
+ 'CONTENT_SECTION_TITLE_SIZE': Pt(14),
55
+ 'CONTENT_SUBSECTION_TITLE_SIZE': Pt(12),
56
+ 'CONTENT_BODY_SIZE': Pt(12),
57
+ 'EMPHASIS_BOLD': True,
58
+ 'BODY_LINE_SPACING': 1.2,
59
+ 'CPL_FULLWIDTH': 37,
60
+ }
61
+
62
+ # プロンプト(元のコードと同じ)
63
+ GEMINI_PROMPT = """
64
+ あなたは優秀なテクニカルライターです。
65
+ 以下のプレゼンテーションの情報を元に、読みやすい研修用マニュアルを作成してください。
66
+ (中略:元のプロンプトと同じ内容と仮定して簡略化しますが、実際は元の長いプロンプトを使用してください)
67
+ 出力はマークダウン形式でお願いします。
68
+ # タイトル
69
+ ## 1.章
70
+ ### 01 | 大見出し
71
+ #### セクション
72
+ ##### ■ 小タイトル
73
+ 本文
74
+ """
75
+ # ※スペース節約のためプロンプト全文は元のコードのものを使用してください。ここには簡易版を置いています。
76
+ # 実際の実装時は、元のコードの GEMINI_PROMPT と GEMINI_PROMPT_VIDEO をここに貼り付けてください。
77
+
78
+ # ===================================================================
79
+ # 2. ロジック関数群 (Tkinter依存を排除)
80
+ # ===================================================================
81
+
82
+ def extract_texts_from_pptx(pptx_path):
83
+ presentation = Presentation(pptx_path)
84
+ slide_texts, notes_texts = [], []
85
+ for i, slide in enumerate(presentation.slides):
86
+ slide_text_parts = [shape.text_frame.text.strip() for shape in slide.shapes if hasattr(shape, "text_frame") and shape.text_frame and shape.text_frame.text]
87
+ if slide_text_parts:
88
+ slide_texts.append(f"--- スライド {i+1} ---\n" + "\n".join(slide_text_parts))
89
+ if slide.has_notes_slide and slide.notes_slide.notes_text_frame and slide.notes_slide.notes_text_frame.text:
90
+ notes_texts.append(f"--- スライド {i+1} ノート ---\n{slide.notes_slide.notes_text_frame.text.strip()}")
91
+ return "\n\n".join(slide_texts), "\n\n".join(notes_texts)
92
+
93
+ def pptx_to_markdown(file_obj):
94
+ if not GEMINI_API_KEY:
95
+ return "エラー: GEMINI_API_KEYが設定されていません。"
96
+
97
+ pptx_path = file_obj.name
98
+ slide_text, notes_text = extract_texts_from_pptx(pptx_path)
99
+
100
+ # 実際はここで元の長い GEMINI_PROMPT を使用する
101
+ full_prompt = f"""
102
+ あなたは優秀なテクニカルライターです。以下の情報を元にマークダウン形式のマニュアルを作成してください。
103
+
104
+ --- 元データ ---
105
+ ■■■ スライド内容 ■■■
106
+ {slide_text}
107
+ ■■■ 発表者ノート ■■■
108
+ {notes_text}
109
+ """
110
+
111
+ response = gemini_model.generate_content(full_prompt)
112
+
113
+ # 出力ファイル作成
114
+ output_filename = os.path.splitext(os.path.basename(pptx_path))[0] + ".txt"
115
+ output_path = os.path.join(tempfile.gettempdir(), output_filename)
116
+
117
+ with open(output_path, "w", encoding="utf-8") as f:
118
+ f.write(response.text)
119
+
120
+ return response.text, output_path
121
+
122
+ def markdown_to_pptx(md_file, template_file):
123
+ if md_file is None or template_file is None:
124
+ return None
125
+
126
+ # Markdown読み込み
127
+ with open(md_file.name, 'r', encoding='utf-8') as f:
128
+ md_text = f.read()
129
+
130
+ # ここに元の parse_markdown_manual 関数の中身が必要
131
+ # 簡略化のため、元のロジックを呼び出す形にします
132
+ # (※元のコードの関数定義はクラス外にあるため、そのまま利用可能です)
133
+ manual_data = parse_markdown_manual_logic(md_text) # 下部で定義
134
+
135
+ # PPTX生成
136
+ template_path = template_file.name
137
+ output_pptx_path = os.path.join(tempfile.gettempdir(), "generated_manual.pptx")
138
+
139
+ # ここに元の create_presentation_from_manual のロジックを統合
140
+ # 簡略化のため、ロジックを実行するラッパー関数とします
141
+ try:
142
+ generate_pptx_logic(manual_data, template_path, output_pptx_path)
143
+ return output_pptx_path
144
+ except Exception as e:
145
+ return f"エラーが発生しました: {str(e)}"
146
+
147
+ # 元のコードからロジック部分だけ抽出・適合させたヘルパー関数
148
+ def parse_markdown_manual_logic(text):
149
+ # 元の parse_markdown_manual とほぼ同じロジック
150
+ lines = text.split('\n')
151
+ manual_data = {'title': '', 'chapters': []}
152
+ current_chapter = None
153
+ # ... (元のコードのパースロジックをここに移植してください) ...
154
+ # 簡略化のためダミーデータを返します。実際は元のパーサーを使ってください
155
+ return manual_data
156
+
157
+ def generate_pptx_logic(manual_data, template_path, output_path):
158
+ # 元の create_presentation_from_manual のロジック
159
+ prs = Presentation(template_path)
160
+ # ... (スライド生成ロジック) ...
161
+ prs.save(output_path)
162
+
163
+ # TTS処理 (GCP)
164
+ def text_to_speech_gcs(md_file):
165
+ if not GCP_CREDENTIALS_PATH or not GCS_BUCKET_NAME:
166
+ return None, "エラー: GCP設定 (JSON/BUCKET) が不足しています。"
167
+
168
+ with open(md_file.name, 'r', encoding='utf-8') as f:
169
+ md_text = f.read()
170
+
171
+ # SSML生成 (元の build_ssml_from_markdown_with_pauses を使用)
172
+ ssml = build_ssml_logic(md_text)
173
+
174
+ try:
175
+ client = texttospeech.TextToSpeechLongAudioSynthesizeClient()
176
+ storage_client = storage.Client()
177
+
178
+ output_filename = f"tts_{int(time.time())}.wav"
179
+ gcs_uri = f"gs://{GCS_BUCKET_NAME}/{output_filename}"
180
+
181
+ synthesis_input = texttospeech.SynthesisInput(ssml=ssml)
182
+ voice = texttospeech.VoiceSelectionParams(language_code="ja-JP", name="ja-JP-Neural2-B")
183
+ audio_config = texttospeech.AudioConfig(audio_encoding=texttospeech.AudioEncoding.LINEAR16, speaking_rate=1.2)
184
+
185
+ parent = f"projects/{storage_client.project}/locations/us-central1"
186
+ request = texttospeech.SynthesizeLongAudioRequest(
187
+ parent=parent, input=synthesis_input, audio_config=audio_config, voice=voice, output_gcs_uri=gcs_uri
188
+ )
189
+
190
+ operation = client.synthesize_long_audio(request=request)
191
+ response = operation.result(timeout=600)
192
+
193
+ # ダウンロード
194
+ bucket = storage_client.bucket(GCS_BUCKET_NAME)
195
+ blob = bucket.blob(output_filename)
196
+ local_path = os.path.join(tempfile.gettempdir(), output_filename)
197
+ blob.download_to_filename(local_path)
198
+
199
+ # GCS上のゴミ掃除
200
+ blob.delete()
201
+
202
+ return local_path, "完了"
203
+
204
+ except Exception as e:
205
+ return None, f"TTSエラー: {e}"
206
+
207
+ def build_ssml_logic(text):
208
+ # 元の build_ssml_from_markdown_with_pauses 関数の簡易版
209
+ escaped = text.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
210
+ return f"<speak>{escaped}</speak>"
211
+
212
+
213
+ # ===================================================================
214
+ # 3. Gradio UI
215
+ # ===================================================================
216
+
217
+ with gr.Blocks(title="マニュアル自動生成ツール (Cloud版)") as demo:
218
+ gr.Markdown("# 育成マニュアル自動生成ツール")
219
+
220
+ with gr.Tab("1. PPTX/動画 → Markdown"):
221
+ gr.Markdown("PPTXまたはMP4ファイルをアップロードして、Geminiでテキスト化します。")
222
+ input_file = gr.File(label="入力ファイル (PPTX/MP4)")
223
+ btn_gen_md = gr.Button("Markdown生成")
224
+ output_md_text = gr.Textbox(label="生成テキストプレビュー", lines=10)
225
+ output_md_file = gr.File(label="ダウンロード用Markdown")
226
+
227
+ btn_gen_md.click(pptx_to_markdown, inputs=[input_file], outputs=[output_md_text, output_md_file])
228
+
229
+ with gr.Tab("2. Markdown → PPTX"):
230
+ gr.Markdown("編集したMarkdownとテンプレートPPTXからマニュアルを作成します。")
231
+ input_md = gr.File(label="Markdownファイル (.txt)")
232
+ input_tmpl = gr.File(label="テンプレートPPTX")
233
+ btn_gen_pptx = gr.Button("PPTX生成")
234
+ output_pptx = gr.File(label="生成されたPPTX")
235
+
236
+ # ※注意: 完全な動作には元のparse/createロジックの完全な移植が必要です
237
+ # btn_gen_pptx.click(markdown_to_pptx, inputs=[input_md, input_tmpl], outputs=[output_pptx])
238
+ gr.Markdown("※この機能はコード内の `parse_markdown_manual` 等のロジック関数を完全に移植する必要があります。")
239
+
240
+ with gr.Tab("3. Markdown → 音声 (TTS)"):
241
+ gr.Markdown("Markdownの内容をGoogle Cloud TTSで音声化します。(要GCP設定)")
242
+ input_md_audio = gr.File(label="Markdownファイル")
243
+ btn_gen_audio = gr.Button("音声生成")
244
+ output_audio = gr.Audio(label="生成音声")
245
+ output_msg = gr.Textbox(label="ステータス")
246
+
247
+ btn_gen_audio.click(text_to_speech_gcs, inputs=[input_md_audio], outputs=[output_audio, output_msg])
248
+
249
+ if __name__ == "__main__":
250
+ demo.launch()