Spaces:
Sleeping
Sleeping
| # app_v4_japanese.py - 日本語版(ドットパーティクル) | |
| import gradio as gr | |
| import numpy as np | |
| import matplotlib.pyplot as plt | |
| from matplotlib.patches import Circle, Ellipse, Polygon, Wedge, FancyBboxPatch | |
| from matplotlib import patheffects | |
| import matplotlib.patches as mpatches | |
| import io | |
| from PIL import Image, ImageDraw, ImageFont | |
| import random | |
| import json | |
| from datetime import datetime | |
| import base64 | |
| import librosa | |
| import librosa.display | |
| import colorsys | |
| import japanize_matplotlib | |
| # グローバル変数 | |
| history = [] | |
| # 花言葉と色の定義(日本語版) | |
| FLOWER_MEANINGS = { | |
| "joy": { | |
| "name": "太陽の花", | |
| "meaning": "明るい希望、前向きな心、幸福への感謝", | |
| "advice": "この調子で素敵な1日を過ごしてください!笑顔は周りも幸せにします。", | |
| "colors": ["#FFD700", "#FFA500", "#FF69B4", "#FFB6C1", "#FFEB3B"], | |
| "bg_gradient": ["#FFF8DC", "#FFFACD"] | |
| }, | |
| "anger": { | |
| "name": "情熱の薔薇", | |
| "meaning": "強い意志、燃える情熱、変革への力", | |
| "advice": "そのエネルギーを建設的な方向に向けてみましょう。深呼吸も大切です。", | |
| "colors": ["#DC143C", "#FF0000", "#8B0000", "#FF4500", "#B22222"], | |
| "bg_gradient": ["#FFE4E1", "#FFF0F5"] | |
| }, | |
| "sadness": { | |
| "name": "勿忘草", | |
| "meaning": "深い思い、内省の時、癒しへの一歩", | |
| "advice": "ゆっくり休んで、自分に優しくしてください。明日はきっと良い日になります。", | |
| "colors": ["#4169E1", "#0000CD", "#191970", "#6495ED", "#4682B4"], | |
| "bg_gradient": ["#F0F8FF", "#E6E6FA"] | |
| }, | |
| "neutral": { | |
| "name": "菫", | |
| "meaning": "落ち着き、バランス、穏やかな心", | |
| "advice": "安定した心の状態です。何か新しいことを始めるのに良い時期かもしれません。", | |
| "colors": ["#DDA0DD", "#D8BFD8", "#E6E6FA", "#B0C4DE", "#9370DB"], | |
| "bg_gradient": ["#F5F5F5", "#FAFAFA"] | |
| } | |
| } | |
| def analyze_emotion_with_waveform(audio_path): | |
| """音声分析と波形データの取得(scipy修正版)""" | |
| if audio_path is None: | |
| return None, None | |
| try: | |
| # 音声を読み込み | |
| y, sr = librosa.load(audio_path, duration=30) | |
| # 基本的な音声特徴(scipyエラーを回避) | |
| try: | |
| from scipy.signal.windows import hann | |
| except (ImportError, AttributeError): | |
| def hann(M): | |
| return 0.5 - 0.5 * np.cos(2.0 * np.pi * np.arange(M) / (M - 1)) | |
| # ピッチ抽出(簡略化) | |
| pitch_values = [] | |
| for i in range(0, len(y) - 2048, 512): | |
| frame = y[i:i+2048] | |
| autocorr = np.correlate(frame, frame, mode='full')[len(frame)-1:] | |
| pitch_values.append(np.mean(autocorr[:100])) | |
| pitch_mean = np.mean(pitch_values) if pitch_values else 0 | |
| # エネルギー | |
| rms = librosa.feature.rms(y=y)[0] | |
| energy_mean = np.mean(rms) | |
| energy_std = np.std(rms) | |
| # スペクトラルセントロイド | |
| cent = librosa.feature.spectral_centroid(y=y, sr=sr)[0] | |
| brightness = np.mean(cent) | |
| # 感情推定 | |
| emotions = { | |
| "joy": 0.25, | |
| "anger": 0.25, | |
| "sadness": 0.25, | |
| "neutral": 0.25 | |
| } | |
| # 特徴に基づく感情調整 | |
| if pitch_mean > 0.5: | |
| emotions["joy"] += 0.2 | |
| else: | |
| emotions["sadness"] += 0.1 | |
| if energy_mean > 0.1: | |
| emotions["joy"] += 0.15 | |
| emotions["anger"] += 0.15 | |
| else: | |
| emotions["sadness"] += 0.15 | |
| if energy_std > 0.1: | |
| emotions["anger"] += 0.2 | |
| else: | |
| emotions["neutral"] += 0.2 | |
| # 正規化 | |
| total = sum(emotions.values()) | |
| emotions = {k: max(0, min(1, v/total)) for k, v in emotions.items()} | |
| # 波形データ | |
| waveform_data = (y, sr) | |
| return emotions, waveform_data | |
| except Exception as e: | |
| print(f"Error in analyze_emotion_with_waveform: {e}") | |
| # フォールバック | |
| emotions = { | |
| "joy": random.random(), | |
| "anger": random.random(), | |
| "sadness": random.random(), | |
| "neutral": random.random() | |
| } | |
| total = sum(emotions.values()) | |
| emotions = {k: v/total for k, v in emotions.items()} | |
| return emotions, None | |
| def create_beautiful_flower_v4(emotions): | |
| """超美しい花を生成(ドットパーティクル版)""" | |
| dominant = max(emotions, key=emotions.get) | |
| style = FLOWER_MEANINGS[dominant] | |
| # 大きめのキャンバス | |
| fig, ax = plt.subplots(figsize=(10, 10)) | |
| ax.set_xlim(-3, 3) | |
| ax.set_ylim(-3, 3) | |
| ax.set_aspect('equal') | |
| ax.axis('off') | |
| # 背景グラデーション | |
| for i in range(100): | |
| alpha = 0.01 | |
| y = -3 + i * 0.06 | |
| rect = plt.Rectangle((-3, y), 6, 0.06, | |
| facecolor=style["bg_gradient"][0] if i < 50 else style["bg_gradient"][1], | |
| alpha=alpha * 2) | |
| ax.add_patch(rect) | |
| # 感情の強さでサイズ調整 | |
| emotion_strength = emotions[dominant] | |
| base_size = 1.0 + emotion_strength * 0.5 | |
| # 花びらの数 | |
| petal_counts = {"joy": 12, "anger": 8, "sadness": 9, "neutral": 10} | |
| petal_count = petal_counts[dominant] | |
| # 花びらを描画 | |
| for layer in range(2): | |
| layer_size = base_size * (0.8 if layer == 0 else 1.0) | |
| layer_alpha = 0.6 if layer == 0 else 0.8 | |
| for i in range(petal_count): | |
| angle = i * 2 * np.pi / petal_count | |
| if layer == 0: | |
| angle += np.pi / petal_count | |
| color_idx = i % len(style["colors"]) | |
| color = style["colors"][color_idx] | |
| # 花びらの形状 | |
| if dominant == "joy": | |
| # 丸い花びら | |
| petal_x = np.cos(angle) * layer_size | |
| petal_y = np.sin(angle) * layer_size | |
| petal = Circle((petal_x, petal_y), | |
| layer_size * 0.5, | |
| facecolor=color, | |
| alpha=layer_alpha, | |
| edgecolor='white', | |
| linewidth=1) | |
| ax.add_patch(petal) | |
| elif dominant == "anger": | |
| # 尖った花びら | |
| spike_points = [] | |
| for r in [0, layer_size * 1.5, layer_size * 0.3]: | |
| spike_angle = angle + (0 if r == layer_size * 1.5 else random.uniform(-0.2, 0.2)) | |
| spike_points.append((np.cos(spike_angle) * r, np.sin(spike_angle) * r)) | |
| petal = Polygon(spike_points, | |
| facecolor=color, | |
| alpha=layer_alpha, | |
| edgecolor='darkred', | |
| linewidth=2) | |
| ax.add_patch(petal) | |
| elif dominant == "sadness": | |
| # 垂れた花びら | |
| theta = np.linspace(angle - 0.3, angle + 0.3, 50) | |
| r = layer_size * (1.2 - 0.4 * np.cos(4 * (theta - angle))) | |
| x = r * np.cos(theta) | |
| y = r * np.sin(theta) - 0.3 * layer_size | |
| vertices = list(zip(x, y)) | |
| vertices.append((0, 0)) | |
| petal = Polygon(vertices, | |
| facecolor=color, | |
| alpha=layer_alpha * 0.7, | |
| edgecolor='navy', | |
| linewidth=1.5) | |
| ax.add_patch(petal) | |
| else: # neutral | |
| # エレガントな楕円形 | |
| petal = Ellipse((np.cos(angle) * layer_size * 0.8, | |
| np.sin(angle) * layer_size * 0.8), | |
| layer_size * 0.8, layer_size * 0.5, | |
| angle=angle * 180 / np.pi + 15, | |
| facecolor=color, | |
| alpha=layer_alpha, | |
| edgecolor='white', | |
| linewidth=1.5) | |
| ax.add_patch(petal) | |
| # 花の中心 | |
| center_colors = { | |
| "joy": ["#FFD700", "#FFA500", "#FF8C00"], | |
| "anger": ["#8B0000", "#DC143C", "#FF0000"], | |
| "sadness": ["#000080", "#4169E1", "#6495ED"], | |
| "neutral": ["#9370DB", "#8A2BE2", "#9932CC"] | |
| } | |
| for i in range(5): | |
| radius = base_size * 0.4 * (1 - i * 0.15) | |
| center = Circle((0, 0), radius, | |
| facecolor=center_colors[dominant][min(i, 2)], | |
| alpha=0.9 - i * 0.15, | |
| edgecolor='white', | |
| linewidth=0.5) | |
| ax.add_patch(center) | |
| # ドットパーティクル効果 | |
| particle_colors = { | |
| "joy": "#FFD700", | |
| "anger": "#FF4500", | |
| "sadness": "#4169E1", | |
| "neutral": "#9370DB" | |
| } | |
| # きらきらドットを追加 | |
| for _ in range(60): | |
| px = random.uniform(-2.8, 2.8) | |
| py = random.uniform(-2.8, 2.8) | |
| size = random.uniform(5, 50) | |
| # 中心からの距離で透明度を調整 | |
| distance = np.sqrt(px**2 + py**2) | |
| alpha = random.uniform(0.1, 0.4) * (1 - distance/4) | |
| ax.scatter(px, py, s=size, | |
| c=particle_colors[dominant], | |
| alpha=max(0, alpha), | |
| marker='o', | |
| edgecolors='none') | |
| # タイトル(日本語)- アスキー文字で表現 | |
| ax.text(0, -2.8, style["name"], fontsize=20, ha='center', | |
| weight='bold', color='#333', style='italic') | |
| buf = io.BytesIO() | |
| plt.savefig(buf, format='png', dpi=150, bbox_inches='tight', | |
| facecolor='white', edgecolor='none') | |
| plt.close() | |
| buf.seek(0) | |
| return Image.open(buf) | |
| def create_waveform_visualization(waveform_data): | |
| """美しい波形ビジュアライゼーション""" | |
| if waveform_data is None: | |
| # ダミーデータで表示 | |
| fig, ax = plt.subplots(figsize=(10, 4)) | |
| t = np.linspace(0, 1, 1000) | |
| wave = np.sin(2 * np.pi * 5 * t) * np.exp(-t * 3) | |
| ax.plot(t, wave, color='#4169E1', linewidth=2) | |
| ax.set_title('Audio Waveform', fontsize=14) | |
| ax.set_xlabel('Time') | |
| ax.set_ylabel('Amplitude') | |
| ax.grid(True, alpha=0.3) | |
| else: | |
| y, sr = waveform_data | |
| fig, ax = plt.subplots(figsize=(10, 4)) | |
| # 波形表示 | |
| librosa.display.waveshow(y, sr=sr, ax=ax, color='#4169E1', alpha=0.8) | |
| ax.set_title('Audio Waveform', fontsize=14) | |
| ax.set_xlabel('Time (s)') | |
| ax.set_ylabel('Amplitude') | |
| ax.grid(True, alpha=0.3) | |
| plt.tight_layout() | |
| buf = io.BytesIO() | |
| plt.savefig(buf, format='png', dpi=120) | |
| plt.close() | |
| buf.seek(0) | |
| return Image.open(buf) | |
| def create_emotion_timeline(): | |
| """感情の変化を美しく可視化""" | |
| if len(history) < 2: | |
| return None | |
| fig, ax = plt.subplots(figsize=(10, 6)) | |
| # データ準備 | |
| emotions_over_time = { | |
| "joy": [], "anger": [], "sadness": [], "neutral": [] | |
| } | |
| for entry in history[-10:]: # 最新10件 | |
| for emotion in emotions_over_time: | |
| emotions_over_time[emotion].append(entry["emotions"][emotion]) | |
| x = range(len(emotions_over_time["joy"])) | |
| # ラインプロット | |
| colors = {"joy": "#FFD700", "anger": "#FF4500", "sadness": "#4169E1", "neutral": "#9370DB"} | |
| labels = {"joy": "Joy", "anger": "Anger", "sadness": "Sadness", "neutral": "Neutral"} | |
| for emotion, values in emotions_over_time.items(): | |
| ax.plot(x, values, color=colors[emotion], marker='o', | |
| markersize=8, linewidth=2, alpha=0.8, label=labels[emotion]) | |
| ax.set_ylabel('Emotion Intensity', fontsize=12) | |
| ax.set_xlabel('Timeline', fontsize=12) | |
| ax.set_title('Emotional Journey', fontsize=16) | |
| ax.legend(loc='upper right') | |
| ax.grid(True, alpha=0.3) | |
| ax.set_ylim(-0.05, 1.05) | |
| plt.tight_layout() | |
| buf = io.BytesIO() | |
| plt.savefig(buf, format='png', dpi=120) | |
| plt.close() | |
| buf.seek(0) | |
| return Image.open(buf) | |
| def create_emotion_card_v4(image, emotions, dominant_emotion): | |
| """美しい感情カード(日本語版)""" | |
| style = FLOWER_MEANINGS[dominant_emotion] | |
| # カード作成 | |
| fig = plt.figure(figsize=(8, 10)) | |
| # レイアウト | |
| gs = fig.add_gridspec(3, 1, height_ratios=[4, 2, 1]) | |
| # 花の画像 | |
| ax1 = fig.add_subplot(gs[0]) | |
| ax1.imshow(image) | |
| ax1.axis('off') | |
| # 花言葉セクション(英語フォントで日本語を回避) | |
| ax2 = fig.add_subplot(gs[1]) | |
| ax2.axis('off') | |
| # テキスト | |
| y_pos = 0.8 | |
| ax2.text(0.5, y_pos, style["name"], | |
| ha='center', fontsize=18, weight='bold') | |
| # 感情データ | |
| ax3 = fig.add_subplot(gs[2]) | |
| ax3.axis('off') | |
| emotion_labels = { | |
| "joy": "Joy", | |
| "anger": "Anger", | |
| "sadness": "Sadness", | |
| "neutral": "Neutral" | |
| } | |
| emotion_text = " | ".join([f"{emotion_labels[k]}: {v:.0%}" for k, v in emotions.items()]) | |
| ax3.text(0.5, 0.5, emotion_text, ha='center', fontsize=10) | |
| plt.tight_layout() | |
| buf = io.BytesIO() | |
| plt.savefig(buf, format='png', dpi=150, bbox_inches='tight', facecolor='white') | |
| plt.close() | |
| buf.seek(0) | |
| return Image.open(buf) | |
| # 履歴管理 | |
| def save_to_history(image, emotions): | |
| timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") | |
| buffered = io.BytesIO() | |
| image.save(buffered, format="PNG") | |
| img_str = base64.b64encode(buffered.getvalue()).decode() | |
| entry = { | |
| "timestamp": timestamp, | |
| "emotions": emotions, | |
| "dominant_emotion": max(emotions, key=emotions.get), | |
| "image_base64": img_str | |
| } | |
| history.append(entry) | |
| if len(history) > 20: | |
| history.pop(0) | |
| def process_voice_v4(audio): | |
| """メイン処理(日本語版)""" | |
| if audio is None: | |
| return None, None, None, None, None, "🎤 録音してください", None | |
| try: | |
| # 感情分析と波形データ取得 | |
| emotions, waveform_data = analyze_emotion_with_waveform(audio) | |
| # 美しい花を生成 | |
| flower = create_beautiful_flower_v4(emotions) | |
| # 波形ビジュアライゼーション | |
| waveform_viz = create_waveform_visualization(waveform_data) | |
| # 最も強い感情 | |
| dominant = max(emotions, key=emotions.get) | |
| confidence = emotions[dominant] | |
| style = FLOWER_MEANINGS[dominant] | |
| # 感情カード | |
| emotion_card = create_emotion_card_v4(flower, emotions, dominant) | |
| # 履歴に保存 | |
| save_to_history(flower, emotions) | |
| # 感情タイムライン | |
| timeline = create_emotion_timeline() | |
| # 説明文(日本語) | |
| emotion_names = { | |
| "joy": "喜び", | |
| "anger": "怒り", | |
| "sadness": "悲しみ", | |
| "neutral": "中立" | |
| } | |
| desc = f""" | |
| 🌸 **{style['name']}**が咲きました! | |
| 📊 **感情分析結果** (確信度: {confidence*100:.0f}%) | |
| • 😊 喜び: {emotions['joy']*100:.1f}% | |
| • 😠 怒り: {emotions['anger']*100:.1f}% | |
| • 😢 悲しみ: {emotions['sadness']*100:.1f}% | |
| • 😐 中立: {emotions['neutral']*100:.1f}% | |
| 💭 **花言葉** | |
| 「{style['meaning']}」 | |
| 💡 **アドバイス** | |
| {style['advice']} | |
| """ | |
| # 7個の出力を返す | |
| return flower, emotions, waveform_viz, emotion_card, timeline, desc, None | |
| except Exception as e: | |
| error_msg = f"エラー: {str(e)}" | |
| print(error_msg) | |
| # エラー時も7個の出力を返す | |
| return None, None, None, None, None, None, error_msg | |
| # カスタムCSS | |
| custom_css = """ | |
| .gradio-container { | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| } | |
| .gr-button-primary { | |
| background: linear-gradient(45deg, #ff6ec7, #ffb347) !important; | |
| border: none !important; | |
| color: white !important; | |
| font-weight: bold; | |
| } | |
| """ | |
| # Gradioインターフェース | |
| with gr.Blocks(theme=gr.themes.Soft(), css=custom_css) as demo: | |
| gr.Markdown( | |
| """ | |
| # 🌸 声の感情から花を生成 - Visual Enhanced Edition 🌸 | |
| ### あなたの感情を美しい花と花言葉で表現します | |
| """ | |
| ) | |
| with gr.Tab("🎤 録音と花の生成"): | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| audio_input = gr.Audio( | |
| sources=["microphone"], | |
| type="filepath", | |
| label="🎤 感情を込めて話してください" | |
| ) | |
| analyze_btn = gr.Button( | |
| "🌺 美しい花を生成", | |
| variant="primary", | |
| size="lg" | |
| ) | |
| with gr.Accordion("💡 より良い結果のコツ", open=False): | |
| gr.Markdown(""" | |
| - 5秒以上話すと精度が向上します | |
| - 感情を込めて自然に話してください | |
| - 静かな環境で録音してください | |
| """) | |
| with gr.Column(scale=1): | |
| flower_output = gr.Image( | |
| label="生成された花", | |
| type="pil" | |
| ) | |
| emotion_output = gr.Label( | |
| label="感情分析結果", | |
| num_top_classes=4 | |
| ) | |
| with gr.Tab("📊 詳細分析"): | |
| with gr.Row(): | |
| with gr.Column(): | |
| waveform_output = gr.Image( | |
| label="音声波形", | |
| type="pil" | |
| ) | |
| with gr.Column(): | |
| description_output = gr.Textbox( | |
| label="花言葉とアドバイス", | |
| lines=12 | |
| ) | |
| with gr.Tab("🎴 感情カード"): | |
| emotion_card_output = gr.Image( | |
| label="感情カード(保存・共有用)", | |
| type="pil" | |
| ) | |
| gr.Markdown("右クリックで画像を保存できます") | |
| with gr.Tab("📈 感情の履歴"): | |
| timeline_output = gr.Image( | |
| label="感情の変化グラフ", | |
| type="pil" | |
| ) | |
| gr.Markdown("※ セッション中の履歴(最大20件)") | |
| error_output = gr.Textbox( | |
| label="エラーメッセージ", | |
| visible=False | |
| ) | |
| # イベントハンドラー(7個の出力) | |
| analyze_btn.click( | |
| fn=process_voice_v4, | |
| inputs=audio_input, | |
| outputs=[ | |
| flower_output, | |
| emotion_output, | |
| waveform_output, | |
| emotion_card_output, | |
| timeline_output, | |
| description_output, | |
| error_output | |
| ] | |
| ) | |
| gr.Markdown(""" | |
| --- | |
| ### 🌺 花の種類と意味 | |
| | 感情 | 花の名前 | 特徴 | | |
| |------|----------|------| | |
| | 😊 喜び | 太陽の花 | 12枚の丸い花びら、暖色系、金色のドット | | |
| | 😠 怒り | 情熱の薔薇 | 8枚の尖った花びら、赤系、オレンジのドット | | |
| | 😢 悲しみ | 勿忘草 | 9枚の垂れた花びら、青系、青いドット | | |
| | 😐 中立 | 菫 | 10枚の優雅な花びら、紫系、紫のドット | | |
| Created with ❤️ using Gradio and Matplotlib | |
| """) | |
| if __name__ == "__main__": | |
| demo.launch() |