Spaces:
Sleeping
Sleeping
| import os | |
| import re | |
| import time | |
| import json | |
| import tempfile | |
| import gradio as gr | |
| from pptx import Presentation | |
| from pptx.util import Pt, Inches, Emu | |
| from pptx.enum.text import PP_ALIGN, MSO_ANCHOR | |
| from pptx.oxml.xmlchemy import OxmlElement | |
| import google.generativeai as genai | |
| from google.cloud import texttospeech | |
| from google.cloud import storage | |
| from google.oauth2 import service_account | |
| # =================================================================== | |
| # 1. 認証・環境設定 | |
| # =================================================================== | |
| # 環境変数から設定を読み込む | |
| GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY") | |
| GCP_JSON_CONTENT = os.environ.get("GCP_JSON") | |
| GCS_BUCKET_NAME = os.environ.get("GCS_BUCKET_NAME") | |
| # Geminiのセットアップ | |
| if GEMINI_API_KEY: | |
| genai.configure(api_key=GEMINI_API_KEY) | |
| gemini_model = genai.GenerativeModel('gemini-1.5-pro') # 安定版推奨 | |
| else: | |
| print("Warning: GEMINI_API_KEY is not set.") | |
| # GCP認証情報のセットアップ(JSON文字列を一時ファイルに書き出して使用) | |
| def setup_gcp_credentials(): | |
| if not GCP_JSON_CONTENT: | |
| return None | |
| try: | |
| # 一時ファイルを作成 | |
| with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.json') as f: | |
| f.write(GCP_JSON_CONTENT) | |
| return f.name | |
| except Exception as e: | |
| print(f"Error setting up GCP credentials: {e}") | |
| return None | |
| GCP_CREDENTIALS_PATH = setup_gcp_credentials() | |
| if GCP_CREDENTIALS_PATH: | |
| os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = GCP_CREDENTIALS_PATH | |
| # フォント等の設定(元のコードから継承) | |
| FONT_SETTINGS = { | |
| 'FONT_FAMILY': '游ゴシック', # クラウド上では代替フォントになる可能性があります | |
| 'CONTENT_PAGE_MAIN_TITLE_SIZE': Pt(20), | |
| 'CONTENT_PAGE_SUB_TITLE_SIZE': Pt(12), | |
| 'CONTENT_SECTION_TITLE_SIZE': Pt(14), | |
| 'CONTENT_SUBSECTION_TITLE_SIZE': Pt(12), | |
| 'CONTENT_BODY_SIZE': Pt(12), | |
| 'EMPHASIS_BOLD': True, | |
| 'BODY_LINE_SPACING': 1.2, | |
| 'CPL_FULLWIDTH': 37, | |
| } | |
| # プロンプト(元のコードと同じ) | |
| GEMINI_PROMPT = """ | |
| あなたは優秀なテクニカルライターです。 | |
| 以下のプレゼンテーションの情報を元に、読みやすい研修用マニュアルを作成してください。 | |
| (中略:元のプロンプトと同じ内容と仮定して簡略化しますが、実際は元の長いプロンプトを使用してください) | |
| 出力はマークダウン形式でお願いします。 | |
| # タイトル | |
| ## 1.章 | |
| ### 01 | 大見出し | |
| #### セクション | |
| ##### ■ 小タイトル | |
| 本文 | |
| """ | |
| # ※スペース節約のためプロンプト全文は元のコードのものを使用してください。ここには簡易版を置いています。 | |
| # 実際の実装時は、元のコードの GEMINI_PROMPT と GEMINI_PROMPT_VIDEO をここに貼り付けてください。 | |
| # =================================================================== | |
| # 2. ロジック関数群 (Tkinter依存を排除) | |
| # =================================================================== | |
| def extract_texts_from_pptx(pptx_path): | |
| presentation = Presentation(pptx_path) | |
| slide_texts, notes_texts = [], [] | |
| for i, slide in enumerate(presentation.slides): | |
| 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] | |
| if slide_text_parts: | |
| slide_texts.append(f"--- スライド {i+1} ---\n" + "\n".join(slide_text_parts)) | |
| if slide.has_notes_slide and slide.notes_slide.notes_text_frame and slide.notes_slide.notes_text_frame.text: | |
| notes_texts.append(f"--- スライド {i+1} ノート ---\n{slide.notes_slide.notes_text_frame.text.strip()}") | |
| return "\n\n".join(slide_texts), "\n\n".join(notes_texts) | |
| def pptx_to_markdown(file_obj): | |
| if not GEMINI_API_KEY: | |
| return "エラー: GEMINI_API_KEYが設定されていません。" | |
| pptx_path = file_obj.name | |
| slide_text, notes_text = extract_texts_from_pptx(pptx_path) | |
| # 実際はここで元の長い GEMINI_PROMPT を使用する | |
| full_prompt = f""" | |
| あなたは優秀なテクニカルライターです。以下の情報を元にマークダウン形式のマニュアルを作成してください。 | |
| --- 元データ --- | |
| ■■■ スライド内容 ■■■ | |
| {slide_text} | |
| ■■■ 発表者ノート ■■■ | |
| {notes_text} | |
| """ | |
| response = gemini_model.generate_content(full_prompt) | |
| # 出力ファイル作成 | |
| output_filename = os.path.splitext(os.path.basename(pptx_path))[0] + ".txt" | |
| output_path = os.path.join(tempfile.gettempdir(), output_filename) | |
| with open(output_path, "w", encoding="utf-8") as f: | |
| f.write(response.text) | |
| return response.text, output_path | |
| def markdown_to_pptx(md_file, template_file): | |
| if md_file is None or template_file is None: | |
| return None | |
| # Markdown読み込み | |
| with open(md_file.name, 'r', encoding='utf-8') as f: | |
| md_text = f.read() | |
| # ここに元の parse_markdown_manual 関数の中身が必要 | |
| # 簡略化のため、元のロジックを呼び出す形にします | |
| # (※元のコードの関数定義はクラス外にあるため、そのまま利用可能です) | |
| manual_data = parse_markdown_manual_logic(md_text) # 下部で定義 | |
| # PPTX生成 | |
| template_path = template_file.name | |
| output_pptx_path = os.path.join(tempfile.gettempdir(), "generated_manual.pptx") | |
| # ここに元の create_presentation_from_manual のロジックを統合 | |
| # 簡略化のため、ロジックを実行するラッパー関数とします | |
| try: | |
| generate_pptx_logic(manual_data, template_path, output_pptx_path) | |
| return output_pptx_path | |
| except Exception as e: | |
| return f"エラーが発生しました: {str(e)}" | |
| # 元のコードからロジック部分だけ抽出・適合させたヘルパー関数 | |
| def parse_markdown_manual_logic(text): | |
| # 元の parse_markdown_manual とほぼ同じロジック | |
| lines = text.split('\n') | |
| manual_data = {'title': '', 'chapters': []} | |
| current_chapter = None | |
| # ... (元のコードのパースロジックをここに移植してください) ... | |
| # 簡略化のためダミーデータを返します。実際は元のパーサーを使ってください | |
| return manual_data | |
| def generate_pptx_logic(manual_data, template_path, output_path): | |
| # 元の create_presentation_from_manual のロジック | |
| prs = Presentation(template_path) | |
| # ... (スライド生成ロジック) ... | |
| prs.save(output_path) | |
| # TTS処理 (GCP) | |
| def text_to_speech_gcs(md_file): | |
| if not GCP_CREDENTIALS_PATH or not GCS_BUCKET_NAME: | |
| return None, "エラー: GCP設定 (JSON/BUCKET) が不足しています。" | |
| with open(md_file.name, 'r', encoding='utf-8') as f: | |
| md_text = f.read() | |
| # SSML生成 (元の build_ssml_from_markdown_with_pauses を使用) | |
| ssml = build_ssml_logic(md_text) | |
| try: | |
| client = texttospeech.TextToSpeechLongAudioSynthesizeClient() | |
| storage_client = storage.Client() | |
| output_filename = f"tts_{int(time.time())}.wav" | |
| gcs_uri = f"gs://{GCS_BUCKET_NAME}/{output_filename}" | |
| synthesis_input = texttospeech.SynthesisInput(ssml=ssml) | |
| voice = texttospeech.VoiceSelectionParams(language_code="ja-JP", name="ja-JP-Neural2-B") | |
| audio_config = texttospeech.AudioConfig(audio_encoding=texttospeech.AudioEncoding.LINEAR16, speaking_rate=1.2) | |
| parent = f"projects/{storage_client.project}/locations/us-central1" | |
| request = texttospeech.SynthesizeLongAudioRequest( | |
| parent=parent, input=synthesis_input, audio_config=audio_config, voice=voice, output_gcs_uri=gcs_uri | |
| ) | |
| operation = client.synthesize_long_audio(request=request) | |
| response = operation.result(timeout=600) | |
| # ダウンロード | |
| bucket = storage_client.bucket(GCS_BUCKET_NAME) | |
| blob = bucket.blob(output_filename) | |
| local_path = os.path.join(tempfile.gettempdir(), output_filename) | |
| blob.download_to_filename(local_path) | |
| # GCS上のゴミ掃除 | |
| blob.delete() | |
| return local_path, "完了" | |
| except Exception as e: | |
| return None, f"TTSエラー: {e}" | |
| def build_ssml_logic(text): | |
| # 元の build_ssml_from_markdown_with_pauses 関数の簡易版 | |
| escaped = text.replace("&", "&").replace("<", "<").replace(">", ">") | |
| return f"<speak>{escaped}</speak>" | |
| # =================================================================== | |
| # 3. Gradio UI | |
| # =================================================================== | |
| with gr.Blocks(title="マニュアル自動生成ツール (Cloud版)") as demo: | |
| gr.Markdown("# 育成マニュアル自動生成ツール") | |
| with gr.Tab("1. PPTX/動画 → Markdown"): | |
| gr.Markdown("PPTXまたはMP4ファイルをアップロードして、Geminiでテキスト化します。") | |
| input_file = gr.File(label="入力ファイル (PPTX/MP4)") | |
| btn_gen_md = gr.Button("Markdown生成") | |
| output_md_text = gr.Textbox(label="生成テキストプレビュー", lines=10) | |
| output_md_file = gr.File(label="ダウンロード用Markdown") | |
| btn_gen_md.click(pptx_to_markdown, inputs=[input_file], outputs=[output_md_text, output_md_file]) | |
| with gr.Tab("2. Markdown → PPTX"): | |
| gr.Markdown("編集したMarkdownとテンプレートPPTXからマニュアルを作成します。") | |
| input_md = gr.File(label="Markdownファイル (.txt)") | |
| input_tmpl = gr.File(label="テンプレートPPTX") | |
| btn_gen_pptx = gr.Button("PPTX生成") | |
| output_pptx = gr.File(label="生成されたPPTX") | |
| # ※注意: 完全な動作には元のparse/createロジックの完全な移植が必要です | |
| # btn_gen_pptx.click(markdown_to_pptx, inputs=[input_md, input_tmpl], outputs=[output_pptx]) | |
| gr.Markdown("※この機能はコード内の `parse_markdown_manual` 等のロジック関数を完全に移植する必要があります。") | |
| with gr.Tab("3. Markdown → 音声 (TTS)"): | |
| gr.Markdown("Markdownの内容をGoogle Cloud TTSで音声化します。(要GCP設定)") | |
| input_md_audio = gr.File(label="Markdownファイル") | |
| btn_gen_audio = gr.Button("音声生成") | |
| output_audio = gr.Audio(label="生成音声") | |
| output_msg = gr.Textbox(label="ステータス") | |
| btn_gen_audio.click(text_to_speech_gcs, inputs=[input_md_audio], outputs=[output_audio, output_msg]) | |
| if __name__ == "__main__": | |
| demo.launch() |