From ee0c00893265b518206adbcda3b10eaf7b63f5c1 Mon Sep 17 00:00:00 2001
From: Claude
Date: Sat, 7 Feb 2026 13:02:24 +0000
Subject: [PATCH 01/21] Fix Cloud Run deployment: bypass DDP init, use /tmp for
writes
Root cause: defaults.py's default_setup() and default_config_parser()
assume a distributed training environment with writable filesystem.
On Cloud Run (read-only /app), this causes silent init failures.
Changes:
- app.py: Skip default_setup() entirely, manually set CPU/single-process config
- app.py: Redirect save_path to /tmp (only writable dir on Cloud Run)
- app.py: Add GCS FUSE mount path resolution with Docker-baked fallback
- cloudbuild.yaml: Add Cloud Storage FUSE volume mount for model serving
- cloudbuild.yaml: Increase max-instances to 4
- Include handoff docs and full LAM_Audio2Expression codebase
https://claude.ai/code/session_01C6n4TZ9PPdx46jCevmVo7P
---
ANALYSIS_REQUEST.md | 199 ++++
LAM_Audio2Expression_HANDOFF.md | 199 ++++
audio2exp-service/.gcloudignore | 12 +
audio2exp-service/.gitignore | 4 +
audio2exp-service/Dockerfile | 32 +
.../LAM_Audio2Expression/.gitignore | 18 +
.../LAM_Audio2Expression/LICENSE | 201 ++++
.../LAM_Audio2Expression/README.md | 123 +++
.../LAM_Audio2Expression/app_lam_audio2exp.py | 313 ++++++
.../assets/images/framework.png | Bin 0 -> 521384 bytes
.../assets/images/logo.jpeg | Bin 0 -> 36452 bytes
.../assets/images/snapshot.png | Bin 0 -> 2126974 bytes
.../assets/images/teaser.jpg | Bin 0 -> 669222 bytes
.../configs/lam_audio2exp_config.py | 92 ++
.../configs/lam_audio2exp_config_streaming.py | 92 ++
.../configs/wav2vec2_config.json | 77 ++
.../LAM_Audio2Expression/engines/__init__.py | 0
.../LAM_Audio2Expression/engines/defaults.py | 147 +++
.../engines/hooks/__init__.py | 5 +
.../engines/hooks/builder.py | 15 +
.../engines/hooks/default.py | 29 +
.../engines/hooks/evaluator.py | 577 ++++++++++++
.../engines/hooks/misc.py | 460 +++++++++
.../LAM_Audio2Expression/engines/infer.py | 295 ++++++
.../LAM_Audio2Expression/engines/launch.py | 135 +++
.../LAM_Audio2Expression/engines/train.py | 299 ++++++
.../LAM_Audio2Expression/inference.py | 48 +
.../inference_streaming_audio.py | 60 ++
.../LAM_Audio2Expression/models/__init__.py | 7 +
.../LAM_Audio2Expression/models/builder.py | 13 +
.../LAM_Audio2Expression/models/default.py | 25 +
.../models/encoder/wav2vec.py | 248 +++++
.../models/encoder/wavlm.py | 87 ++
.../models/losses/__init__.py | 4 +
.../models/losses/builder.py | 28 +
.../models/losses/lovasz.py | 253 +++++
.../models/losses/misc.py | 241 +++++
.../LAM_Audio2Expression/models/network.py | 646 +++++++++++++
.../LAM_Audio2Expression/models/utils.py | 752 +++++++++++++++
.../LAM_Audio2Expression/requirements.txt | 11 +
.../scripts/install/install_cu118.sh | 9 +
.../scripts/install/install_cu121.sh | 9 +
.../LAM_Audio2Expression/utils/__init__.py | 0
.../LAM_Audio2Expression/utils/cache.py | 53 ++
.../LAM_Audio2Expression/utils/comm.py | 192 ++++
.../LAM_Audio2Expression/utils/config.py | 696 ++++++++++++++
.../LAM_Audio2Expression/utils/env.py | 33 +
.../LAM_Audio2Expression/utils/events.py | 585 ++++++++++++
.../LAM_Audio2Expression/utils/logger.py | 167 ++++
.../LAM_Audio2Expression/utils/misc.py | 156 +++
.../LAM_Audio2Expression/utils/optimizer.py | 52 +
.../LAM_Audio2Expression/utils/path.py | 105 +++
.../LAM_Audio2Expression/utils/registry.py | 318 +++++++
.../LAM_Audio2Expression/utils/scheduler.py | 144 +++
.../LAM_Audio2Expression/utils/timer.py | 71 ++
.../utils/visualization.py | 86 ++
...dio_gaussian_render-0.0.3-py3-none-any.whl | Bin 0 -> 1508333 bytes
audio2exp-service/README.md | 201 ++++
audio2exp-service/app.py | 420 +++++++++
audio2exp-service/cloudbuild.yaml | 80 ++
audio2exp-service/cpu_support.patch | 69 ++
audio2exp-service/deploy.ps1 | 69 ++
audio2exp-service/fix_gcs_model.sh | 54 ++
.../app_customer_support_modified.py | 888 ++++++++++++++++++
.../gourmet_support_integration.py | 129 +++
.../integration/gourmet_support_patch.py | 198 ++++
audio2exp-service/requirements.txt | 18 +
audio2exp-service/run_local.sh | 43 +
audio2exp-service/start.sh | 10 +
audio2exp-service/test_client.py | 163 ++++
70 files changed, 10765 insertions(+)
create mode 100644 ANALYSIS_REQUEST.md
create mode 100644 LAM_Audio2Expression_HANDOFF.md
create mode 100644 audio2exp-service/.gcloudignore
create mode 100644 audio2exp-service/.gitignore
create mode 100644 audio2exp-service/Dockerfile
create mode 100644 audio2exp-service/LAM_Audio2Expression/.gitignore
create mode 100644 audio2exp-service/LAM_Audio2Expression/LICENSE
create mode 100644 audio2exp-service/LAM_Audio2Expression/README.md
create mode 100644 audio2exp-service/LAM_Audio2Expression/app_lam_audio2exp.py
create mode 100644 audio2exp-service/LAM_Audio2Expression/assets/images/framework.png
create mode 100644 audio2exp-service/LAM_Audio2Expression/assets/images/logo.jpeg
create mode 100644 audio2exp-service/LAM_Audio2Expression/assets/images/snapshot.png
create mode 100644 audio2exp-service/LAM_Audio2Expression/assets/images/teaser.jpg
create mode 100644 audio2exp-service/LAM_Audio2Expression/configs/lam_audio2exp_config.py
create mode 100644 audio2exp-service/LAM_Audio2Expression/configs/lam_audio2exp_config_streaming.py
create mode 100644 audio2exp-service/LAM_Audio2Expression/configs/wav2vec2_config.json
create mode 100644 audio2exp-service/LAM_Audio2Expression/engines/__init__.py
create mode 100644 audio2exp-service/LAM_Audio2Expression/engines/defaults.py
create mode 100644 audio2exp-service/LAM_Audio2Expression/engines/hooks/__init__.py
create mode 100644 audio2exp-service/LAM_Audio2Expression/engines/hooks/builder.py
create mode 100644 audio2exp-service/LAM_Audio2Expression/engines/hooks/default.py
create mode 100644 audio2exp-service/LAM_Audio2Expression/engines/hooks/evaluator.py
create mode 100644 audio2exp-service/LAM_Audio2Expression/engines/hooks/misc.py
create mode 100644 audio2exp-service/LAM_Audio2Expression/engines/infer.py
create mode 100644 audio2exp-service/LAM_Audio2Expression/engines/launch.py
create mode 100644 audio2exp-service/LAM_Audio2Expression/engines/train.py
create mode 100644 audio2exp-service/LAM_Audio2Expression/inference.py
create mode 100644 audio2exp-service/LAM_Audio2Expression/inference_streaming_audio.py
create mode 100644 audio2exp-service/LAM_Audio2Expression/models/__init__.py
create mode 100644 audio2exp-service/LAM_Audio2Expression/models/builder.py
create mode 100644 audio2exp-service/LAM_Audio2Expression/models/default.py
create mode 100644 audio2exp-service/LAM_Audio2Expression/models/encoder/wav2vec.py
create mode 100644 audio2exp-service/LAM_Audio2Expression/models/encoder/wavlm.py
create mode 100644 audio2exp-service/LAM_Audio2Expression/models/losses/__init__.py
create mode 100644 audio2exp-service/LAM_Audio2Expression/models/losses/builder.py
create mode 100644 audio2exp-service/LAM_Audio2Expression/models/losses/lovasz.py
create mode 100644 audio2exp-service/LAM_Audio2Expression/models/losses/misc.py
create mode 100644 audio2exp-service/LAM_Audio2Expression/models/network.py
create mode 100644 audio2exp-service/LAM_Audio2Expression/models/utils.py
create mode 100644 audio2exp-service/LAM_Audio2Expression/requirements.txt
create mode 100644 audio2exp-service/LAM_Audio2Expression/scripts/install/install_cu118.sh
create mode 100644 audio2exp-service/LAM_Audio2Expression/scripts/install/install_cu121.sh
create mode 100644 audio2exp-service/LAM_Audio2Expression/utils/__init__.py
create mode 100644 audio2exp-service/LAM_Audio2Expression/utils/cache.py
create mode 100644 audio2exp-service/LAM_Audio2Expression/utils/comm.py
create mode 100644 audio2exp-service/LAM_Audio2Expression/utils/config.py
create mode 100644 audio2exp-service/LAM_Audio2Expression/utils/env.py
create mode 100644 audio2exp-service/LAM_Audio2Expression/utils/events.py
create mode 100644 audio2exp-service/LAM_Audio2Expression/utils/logger.py
create mode 100644 audio2exp-service/LAM_Audio2Expression/utils/misc.py
create mode 100644 audio2exp-service/LAM_Audio2Expression/utils/optimizer.py
create mode 100644 audio2exp-service/LAM_Audio2Expression/utils/path.py
create mode 100644 audio2exp-service/LAM_Audio2Expression/utils/registry.py
create mode 100644 audio2exp-service/LAM_Audio2Expression/utils/scheduler.py
create mode 100644 audio2exp-service/LAM_Audio2Expression/utils/timer.py
create mode 100644 audio2exp-service/LAM_Audio2Expression/utils/visualization.py
create mode 100644 audio2exp-service/LAM_Audio2Expression/wheels/gradio_gaussian_render-0.0.3-py3-none-any.whl
create mode 100644 audio2exp-service/README.md
create mode 100644 audio2exp-service/app.py
create mode 100644 audio2exp-service/cloudbuild.yaml
create mode 100644 audio2exp-service/cpu_support.patch
create mode 100644 audio2exp-service/deploy.ps1
create mode 100755 audio2exp-service/fix_gcs_model.sh
create mode 100644 audio2exp-service/integration/app_customer_support_modified.py
create mode 100644 audio2exp-service/integration/gourmet_support_integration.py
create mode 100644 audio2exp-service/integration/gourmet_support_patch.py
create mode 100644 audio2exp-service/requirements.txt
create mode 100755 audio2exp-service/run_local.sh
create mode 100644 audio2exp-service/start.sh
create mode 100644 audio2exp-service/test_client.py
diff --git a/ANALYSIS_REQUEST.md b/ANALYSIS_REQUEST.md
new file mode 100644
index 0000000..ac84b1b
--- /dev/null
+++ b/ANALYSIS_REQUEST.md
@@ -0,0 +1,199 @@
+# LAM_Audio2Expression 解析・実装依頼
+
+## 依頼の背景
+
+Audio2ExpressionサービスをGoogle Cloud Runにデプロイしようと48時間以上、40回以上試行したが、モデルが「mock」モードのままで正しく初期化されない。対症療法的な修正を繰り返しても解決できないため、根本的なアプローチの見直しが必要。
+
+## 前任AIの反省点
+
+**重要**: 前任AI(Claude)は以下の問題を抱えていた:
+
+1. **古い知識ベースからの推論に依存**
+ - 一般的な「Cloud Runデプロイ」パターンを適用しようとした
+ - LAM_Audio2Expression固有の設計思想を理解できていなかった
+
+2. **表面的なコード理解**
+ - コードを読んだが、なぜそのように設計されているかを理解していなかった
+ - 元々どのような環境・ユースケースを想定したコードなのかを考慮しなかった
+
+3. **対症療法の繰り返し**
+ - ログからエラーを見つけ→修正→デプロイ→また別のエラー、の無限ループ
+ - 根本原因を特定せず、見えている症状だけを修正し続けた
+
+4. **思い込み**
+ - 「モデルの読み込みや初期化がうまくいっていない」と決めつけていた
+ - 問題はそこではなく、もっと根本的なアプローチの誤りである可能性がある
+
+**この解析を行う際は、上記の落とし穴にハマらないよう注意してください。**
+
+## 解析対象コード
+
+### 主要ファイル
+
+**1. audio2exp-service/app.py** (現在のサービス実装)
+- FastAPI を使用したWebサービス
+- `/health`, `/debug`, `/api/audio2expression`, `/ws/{session_id}` エンドポイント
+- `Audio2ExpressionEngine` クラスでモデル管理
+
+**2. LAM_Audio2Expression/engines/infer.py**
+- `InferBase` クラス: モデル構築の基底クラス
+- `Audio2ExpressionInfer` クラス: 音声→表情推論
+- `infer_streaming_audio()`: リアルタイムストリーミング推論
+
+**3. LAM_Audio2Expression/models/network.py**
+- `Audio2Expression` クラス: PyTorchニューラルネットワーク
+- wav2vec2 エンコーダー + Identity Encoder + Decoder構成
+
+**4. LAM_Audio2Expression/engines/defaults.py**
+- `default_config_parser()`: 設定ファイル読み込み
+- `default_setup()`: バッチサイズ等の設定計算
+- `create_ddp_model()`: 分散データ並列ラッパー
+
+## 具体的な解析依頼
+
+### Q1: モデル初期化が完了しない根本原因
+
+```python
+# app.py での初期化
+self.infer = INFER.build(dict(type=cfg.infer.type, cfg=cfg))
+self.infer.model.eval()
+```
+
+この処理がCloud Run環境で正常に完了しない理由を特定してください。
+
+考えられる原因:
+- [ ] メモリ不足 (8GiBで足りない?)
+- [ ] CPU環境での動作制限
+- [ ] 分散処理設定が単一インスタンスで問題を起こす
+- [ ] ファイルシステムの書き込み権限
+- [ ] タイムアウト (コールドスタート時間)
+- [ ] その他
+
+### Q2: default_setup() の問題
+
+```python
+# defaults.py
+def default_setup(cfg):
+ world_size = comm.get_world_size() # Cloud Runでは1
+ cfg.num_worker = cfg.num_worker if cfg.num_worker is not None else mp.cpu_count()
+ cfg.num_worker_per_gpu = cfg.num_worker // world_size
+ assert cfg.batch_size % world_size == 0 # 失敗する可能性?
+```
+
+推論時にこの設定が問題を起こしていないか確認してください。
+
+### Q3: ロガー設定の問題
+
+```python
+# infer.py
+self.logger = get_root_logger(
+ log_file=os.path.join(cfg.save_path, "infer.log"),
+ file_mode="a" if cfg.resume else "w",
+)
+```
+
+Cloud Runのファイルシステムでログファイル作成が失敗する可能性を確認してください。
+
+### Q4: wav2vec2 モデル読み込み
+
+```python
+# network.py
+if os.path.exists(pretrained_encoder_path):
+ self.audio_encoder = Wav2Vec2Model.from_pretrained(pretrained_encoder_path)
+else:
+ config = Wav2Vec2Config.from_pretrained(wav2vec2_config_path)
+ self.audio_encoder = Wav2Vec2Model(config) # ランダム重み!
+```
+
+- wav2vec2-base-960h フォルダの構成は正しいか?
+- HuggingFaceからのダウンロードが必要なファイルはないか?
+
+### Q5: 適切なデプロイ方法
+
+Cloud Runが不適切な場合、以下の代替案を検討:
+- Google Compute Engine (GPU インスタンス)
+- Cloud Run Jobs (バッチ処理)
+- Vertex AI Endpoints
+- Kubernetes Engine
+
+## 期待する成果
+
+### 1. 分析結果
+- 根本原因の特定
+- なぜ40回以上の試行で解決できなかったかの説明
+
+### 2. 修正されたコード
+```
+audio2exp-service/
+├── app.py # 修正版
+├── Dockerfile # 必要なら修正
+└── cloudbuild.yaml # 必要なら修正
+```
+
+### 3. 動作確認方法
+```bash
+# ヘルスチェック
+curl https:///health
+# 期待する応答: {"model_initialized": true, "mode": "inference", ...}
+
+# 推論テスト
+curl -X POST https:///api/audio2expression \
+ -H "Content-Type: application/json" \
+ -d '{"audio_base64": "...", "session_id": "test"}'
+```
+
+## 技術スペック
+
+### モデル仕様
+| 項目 | 値 |
+|------|-----|
+| 入力サンプルレート | 24kHz (API) / 16kHz (内部) |
+| 出力フレームレート | 30 fps |
+| 出力次元 | 52 (ARKit blendshape) |
+| モデルファイルサイズ | ~500MB (LAM) + ~400MB (wav2vec2) |
+
+### デプロイ環境
+| 項目 | 値 |
+|------|-----|
+| プラットフォーム | Cloud Run Gen 2 |
+| リージョン | asia-northeast1 |
+| メモリ | 8GiB |
+| CPU | 4 |
+| max-instances | 4 |
+
+### 依存関係 (requirements.txt)
+```
+torch==2.0.1
+torchaudio==2.0.2
+transformers==4.30.2
+librosa==0.10.0
+fastapi==0.100.0
+uvicorn==0.23.0
+numpy==1.24.3
+scipy==1.11.1
+pydantic==2.0.3
+```
+
+## ファイルの場所
+
+```bash
+# プロジェクトルート
+cd /home/user/LAM_gpro
+
+# メインサービス
+cat audio2exp-service/app.py
+
+# 推論エンジン
+cat audio2exp-service/LAM_Audio2Expression/engines/infer.py
+
+# ニューラルネットワーク
+cat audio2exp-service/LAM_Audio2Expression/models/network.py
+
+# 設定
+cat audio2exp-service/LAM_Audio2Expression/engines/defaults.py
+cat audio2exp-service/LAM_Audio2Expression/configs/lam_audio2exp_config_streaming.py
+```
+
+---
+
+以上、よろしくお願いいたします。
diff --git a/LAM_Audio2Expression_HANDOFF.md b/LAM_Audio2Expression_HANDOFF.md
new file mode 100644
index 0000000..c109d4c
--- /dev/null
+++ b/LAM_Audio2Expression_HANDOFF.md
@@ -0,0 +1,199 @@
+# LAM_Audio2Expression 引継ぎ・解析依頼文
+
+## 1. プロジェクト概要
+
+### 目的
+Audio2Expressionサービスを Google Cloud Run にデプロイし、音声からARKit 52 blendshape係数をリアルタイムで生成するAPIを提供する。
+
+### リポジトリ構成
+```
+/home/user/LAM_gpro/
+├── audio2exp-service/
+│ ├── app.py # FastAPI サービス本体
+│ ├── Dockerfile # Dockerイメージ定義
+│ ├── cloudbuild.yaml # Cloud Build設定
+│ ├── requirements.txt # Python依存関係
+│ ├── start.sh # 起動スクリプト
+│ ├── models/ # モデルファイル格納
+│ │ ├── LAM_audio2exp_streaming.tar # LAMモデル重み
+│ │ └── wav2vec2-base-960h/ # wav2vec2事前学習モデル
+│ └── LAM_Audio2Expression/ # LAMモデルソースコード
+│ ├── configs/
+│ │ └── lam_audio2exp_config_streaming.py
+│ ├── engines/
+│ │ ├── defaults.py # 設定パーサー・セットアップ
+│ │ └── infer.py # 推論エンジン (Audio2ExpressionInfer)
+│ ├── models/
+│ │ ├── __init__.py
+│ │ ├── builder.py # モデルビルダー
+│ │ ├── default.py # DefaultEstimator
+│ │ ├── network.py # Audio2Expression ニューラルネットワーク
+│ │ └── utils.py # 後処理ユーティリティ
+│ └── utils/
+│ ├── comm.py # 分散処理ユーティリティ
+│ ├── config.py # 設定管理
+│ ├── env.py # 環境設定
+│ └── logger.py # ロギング
+```
+
+## 2. コア技術アーキテクチャ
+
+### Audio2Expression モデル (network.py)
+
+```python
+# 入力 → 出力フロー
+input_audio_array (24kHz or 16kHz)
+ → wav2vec2 audio_encoder (768次元特徴)
+ → feature_projection (512次元)
+ → identity_encoder (話者特徴 + GRU)
+ → decoder (Conv1D + LayerNorm + ReLU)
+ → output_proj (52次元)
+ → sigmoid
+ → ARKit 52 blendshape coefficients (0-1)
+```
+
+### 重要なパラメータ
+- **内部サンプルレート**: 16kHz
+- **出力フレームレート**: 30 fps
+- **出力次元**: 52 (ARKit blendshape)
+- **identity classes**: 12 (話者ID用)
+
+### wav2vec2の読み込みロジック (network.py:40-44)
+```python
+if os.path.exists(pretrained_encoder_path):
+ self.audio_encoder = Wav2Vec2Model.from_pretrained(pretrained_encoder_path)
+else:
+ # 警告: この場合、ランダム重みで初期化される
+ config = Wav2Vec2Config.from_pretrained(wav2vec2_config_path)
+ self.audio_encoder = Wav2Vec2Model(config)
+```
+
+### ストリーミング推論 (infer.py)
+
+`infer_streaming_audio()` メソッド:
+1. コンテキスト管理 (`previous_audio`, `previous_expression`, `previous_volume`)
+2. 64フレーム最大長でバッファリング
+3. 16kHzへリサンプリング
+4. 後処理パイプライン:
+ - `smooth_mouth_movements()` - 無音時の口動き抑制
+ - `apply_frame_blending()` - フレーム間ブレンディング
+ - `apply_savitzky_golay_smoothing()` - 平滑化フィルタ
+ - `symmetrize_blendshapes()` - 左右対称化
+ - `apply_random_eye_blinks_context()` - 瞬き追加
+
+## 3. 現在の問題
+
+### 症状
+- Cloud Runへのデプロイは成功する
+- ヘルスチェック応答:
+ ```json
+ {
+ "model_initialized": false,
+ "mode": "mock",
+ "init_step": "...",
+ "init_error": "..."
+ }
+ ```
+- 48時間以上、40回以上のデプロイ試行で解決できていない
+
+### 試行した解決策(全て失敗)
+1. gsutil でモデルダウンロード
+2. Python GCSクライアントでモデルダウンロード
+3. Cloud Storage FUSE でマウント
+4. Dockerイメージにモデルを焼き込み
+5. max-instances を 10 → 5 → 4 に削減(quota対策)
+6. ステップ別エラー追跡を追加
+
+### 重要な指摘
+ユーザーからの指摘:
+> 「キミは、モデルの読み込みや、初期化が上手く行ってないと、思い込んでるでしょ?そうじゃなく、根本的にやり方が間違ってるんだよ!」
+> 「LAM_Audio2Expressionのロジックを本質的に理解できてないでしょ?」
+
+つまり、問題は単なる「ファイルが見つからない」「初期化エラー」ではなく、**アプローチ自体が根本的に間違っている**可能性がある。
+
+## 4. 解析依頼事項
+
+### 4.1 根本原因の特定
+1. **LAM_Audio2Expressionの設計思想**
+ - このモデルは元々どのような環境で動作することを想定しているか?
+ - GPU必須か?CPU動作可能か?
+ - リアルタイムストリーミング vs バッチ処理の制約は?
+
+2. **Cloud Run適合性**
+ - コールドスタート時間の問題はないか?
+ - メモリ8GiBで十分か?
+ - CPUのみで実用的な速度が出るか?
+
+3. **初期化プロセス**
+ - `default_setup(cfg)` のバッチサイズ計算が問題を起こしていないか?
+ - `create_ddp_model()` がシングルプロセス環境で正しく動作するか?
+ - ロガー設定がCloud Run環境で問題を起こしていないか?
+
+### 4.2 app.py の問題点
+現在の `app.py` の初期化フローを確認:
+```python
+# lifespan内で非同期初期化
+loop = asyncio.get_event_loop()
+await loop.run_in_executor(None, engine.initialize)
+```
+
+- この初期化方法は正しいか?
+- エラーが正しくキャッチ・伝播されているか?
+
+### 4.3 設定ファイルの問題
+`lam_audio2exp_config_streaming.py`:
+```python
+num_worker = 16 # Cloud Runで問題になる?
+batch_size = 16 # 推論時も必要?
+```
+
+## 5. 期待する成果物
+
+1. **根本原因の分析レポート**
+ - なぜ現在のアプローチが機能しないのか
+ - Cloud Runでこのモデルを動作させることは可能か
+
+2. **正しい実装方針**
+ - 必要な場合、代替デプロイメント方法の提案
+ - app.py の正しい実装
+
+3. **動作する実装コード**
+ - モデル初期化が成功する
+ - `/health` エンドポイントで `model_initialized: true` を返す
+ - `/api/audio2expression` でリアルタイム推論が機能する
+
+## 6. 関連ファイル一覧
+
+### 必読ファイル
+| ファイル | 説明 |
+|---------|------|
+| `audio2exp-service/app.py` | FastAPIサービス本体 |
+| `LAM_Audio2Expression/engines/infer.py` | 推論エンジン |
+| `LAM_Audio2Expression/models/network.py` | ニューラルネットワーク定義 |
+| `LAM_Audio2Expression/engines/defaults.py` | 設定パーサー |
+| `LAM_Audio2Expression/configs/lam_audio2exp_config_streaming.py` | ストリーミング設定 |
+
+### 補助ファイル
+| ファイル | 説明 |
+|---------|------|
+| `LAM_Audio2Expression/models/utils.py` | 後処理ユーティリティ |
+| `LAM_Audio2Expression/utils/comm.py` | 分散処理ユーティリティ |
+| `LAM_Audio2Expression/models/builder.py` | モデルビルダー |
+
+## 7. デプロイ環境
+
+- **Cloud Run Gen 2**
+- **メモリ**: 8GiB
+- **CPU**: 4
+- **max-instances**: 4
+- **コンテナポート**: 8080
+- **リージョン**: asia-northeast1
+
+## 8. Git情報
+
+- **ブランチ**: `claude/implementation-testing-w2xCb`
+- **最新コミット**: `4ba662c Simplify deployment: bake models into Docker image`
+
+---
+
+作成日: 2026-02-07
diff --git a/audio2exp-service/.gcloudignore b/audio2exp-service/.gcloudignore
new file mode 100644
index 0000000..28d1cdd
--- /dev/null
+++ b/audio2exp-service/.gcloudignore
@@ -0,0 +1,12 @@
+# Cloud Build ignore file
+# Unlike .gitignore, we INCLUDE LAM_Audio2Expression for the build
+
+exp/
+models/
+__pycache__/
+*.pyc
+.git/
+.gitignore
+*.md
+*.txt
+!requirements.txt
diff --git a/audio2exp-service/.gitignore b/audio2exp-service/.gitignore
new file mode 100644
index 0000000..8739a60
--- /dev/null
+++ b/audio2exp-service/.gitignore
@@ -0,0 +1,4 @@
+exp/
+models/
+__pycache__/
+*.pyc
diff --git a/audio2exp-service/Dockerfile b/audio2exp-service/Dockerfile
new file mode 100644
index 0000000..b1eaaa9
--- /dev/null
+++ b/audio2exp-service/Dockerfile
@@ -0,0 +1,32 @@
+FROM python:3.10-slim
+
+WORKDIR /app
+
+# System dependencies
+RUN apt-get update && apt-get install -y --no-install-recommends \
+ ffmpeg \
+ && rm -rf /var/lib/apt/lists/*
+
+# Copy and install Python dependencies
+COPY requirements.txt .
+RUN pip install --no-cache-dir -r requirements.txt
+
+# Copy LAM_Audio2Expression code
+COPY LAM_Audio2Expression/ ./LAM_Audio2Expression/
+
+# Copy models directory (downloaded during Cloud Build)
+COPY models/ ./models/
+
+# Copy application code
+COPY app.py .
+COPY start.sh .
+RUN chmod +x start.sh
+
+# Fixed paths - models are baked into image
+ENV LAM_A2E_PATH=/app/LAM_Audio2Expression
+ENV LAM_WEIGHT_PATH=/app/models/LAM_audio2exp_streaming.tar
+ENV WAV2VEC_PATH=/app/models/wav2vec2-base-960h
+
+ENV PORT=8080
+
+CMD ["./start.sh"]
diff --git a/audio2exp-service/LAM_Audio2Expression/.gitignore b/audio2exp-service/LAM_Audio2Expression/.gitignore
new file mode 100644
index 0000000..73c532f
--- /dev/null
+++ b/audio2exp-service/LAM_Audio2Expression/.gitignore
@@ -0,0 +1,18 @@
+image/
+__pycache__
+**/build/
+**/*.egg-info/
+**/dist/
+*.so
+exp
+weights
+data
+log
+outputs/
+.vscode
+.idea
+*/.DS_Store
+TEMP/
+pretrained/
+**/*.out
+Dockerfile
\ No newline at end of file
diff --git a/audio2exp-service/LAM_Audio2Expression/LICENSE b/audio2exp-service/LAM_Audio2Expression/LICENSE
new file mode 100644
index 0000000..f49a4e1
--- /dev/null
+++ b/audio2exp-service/LAM_Audio2Expression/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
\ No newline at end of file
diff --git a/audio2exp-service/LAM_Audio2Expression/README.md b/audio2exp-service/LAM_Audio2Expression/README.md
new file mode 100644
index 0000000..7f9e2c2
--- /dev/null
+++ b/audio2exp-service/LAM_Audio2Expression/README.md
@@ -0,0 +1,123 @@
+# LAM-A2E: Audio to Expression
+
+[](https://aigc3d.github.io/projects/LAM/)
+[](https://www.apache.org/licenses/LICENSE-2.0)
+[](https://www.modelscope.cn/studios/Damo_XR_Lab/LAM-A2E)
+
+## Description
+#### This project leverages audio input to generate ARKit blendshapes-driven facial expressions in ⚡real-time⚡, powering ultra-realistic 3D avatars generated by [LAM](https://github.com/aigc3d/LAM).
+To enable ARKit-driven animation of the LAM model, we adapted ARKit blendshapes to align with FLAME's facial topology through manual customization. The LAM-A2E network follows an encoder-decoder architecture, as shown below. We adopt the state-of-the-art pre-trained speech model Wav2Vec for the audio encoder. The features extracted from the raw audio waveform are combined with style features and fed into the decoder, which outputs stylized blendshape coefficients.
+
+
+

+
+
+## Demo
+
+
+
+
+
+## 📢 News
+
+**[May 21, 2025]** We have released a [Avatar Export Feature](https://www.modelscope.cn/studios/Damo_XR_Lab/LAM_Large_Avatar_Model), enabling users to generate facial expressions from audio using any [LAM-generated](https://github.com/aigc3d/LAM) 3D digital humans.
+**[April 21, 2025]** We have released the [ModelScope](https://www.modelscope.cn/studios/Damo_XR_Lab/LAM-A2E) Space !
+**[April 21, 2025]** We have released the WebGL Interactive Chatting Avatar SDK on [OpenAvatarChat](https://github.com/HumanAIGC-Engineering/OpenAvatarChat) (including LLM, ASR, TTS, Avatar), with which you can freely chat with our generated 3D Digital Human ! 🔥
+
+### To do list
+- [ ] Release Huggingface space.
+- [x] Release [Modelscope demo space](https://www.modelscope.cn/studios/Damo_XR_Lab/LAM-A2E). You can try the demo or pull the demo source code and deploy it on your own machine.
+- [ ] Release the LAM-A2E model based on the Flame expression.
+- [x] Release Interactive Chatting Avatar SDK with [OpenAvatarChat](https://www.modelscope.cn/studios/Damo_XR_Lab/LAM-A2E), including LLM, ASR, TTS, LAM-Avatars.
+
+
+
+## 🚀 Get Started
+### Environment Setup
+```bash
+git clone git@github.com:aigc3d/LAM_Audio2Expression.git
+cd LAM_Audio2Expression
+# Create conda environment (currently only supports Python 3.10)
+conda create -n lam_a2e python=3.10
+# Activate the conda environment
+conda activate lam_a2e
+# Install with Cuda 12.1
+sh ./scripts/install/install_cu121.sh
+# Or Install with Cuda 11.8
+sh ./scripts/install/install_cu118.sh
+```
+
+
+### Download
+
+```
+# HuggingFace download
+# Download Assets and Model Weights
+huggingface-cli download 3DAIGC/LAM_audio2exp --local-dir ./
+tar -xzvf LAM_audio2exp_assets.tar && rm -f LAM_audio2exp_assets.tar
+tar -xzvf LAM_audio2exp_streaming.tar && rm -f LAM_audio2exp_streaming.tar
+
+# Or OSS Download (In case of HuggingFace download failing)
+# Download Assets
+wget https://virutalbuy-public.oss-cn-hangzhou.aliyuncs.com/share/aigc3d/data/LAM/LAM_audio2exp_assets.tar
+tar -xzvf LAM_audio2exp_assets.tar && rm -f LAM_audio2exp_assets.tar
+# Download Model Weights
+wget https://virutalbuy-public.oss-cn-hangzhou.aliyuncs.com/share/aigc3d/data/LAM/LAM_audio2exp_streaming.tar
+tar -xzvf LAM_audio2exp_streaming.tar && rm -f LAM_audio2exp_streaming.tar
+
+Or Modelscope Download
+git clone https://www.modelscope.cn/Damo_XR_Lab/LAM_audio2exp.git ./modelscope_download
+```
+
+
+### Quick Start Guide
+#### Using Gradio Interface:
+We provide a simple Gradio demo with **WebGL Render**, and you can get rendering results by uploading audio in seconds.
+
+[//]: # (
)
+
+
+
+
+
+```
+python app_lam_audio2exp.py
+```
+
+### Inference
+```bash
+# example: python inference.py --config-file configs/lam_audio2exp_config_streaming.py --options save_path=exp/audio2exp weight=pretrained_models/lam_audio2exp_streaming.tar audio_input=./assets/sample_audio/BarackObama_english.wav
+python inference.py --config-file ${CONFIG_PATH} --options save_path=${SAVE_PATH} weight=${CHECKPOINT_PATH} audio_input=${AUDIO_INPUT}
+```
+
+### Acknowledgement
+This work is built on many amazing research works and open-source projects:
+- [FLAME](https://flame.is.tue.mpg.de)
+- [FaceFormer](https://github.com/EvelynFan/FaceFormer)
+- [Meshtalk](https://github.com/facebookresearch/meshtalk)
+- [Unitalker](https://github.com/X-niper/UniTalker)
+- [Pointcept](https://github.com/Pointcept/Pointcept)
+
+Thanks for their excellent works and great contribution.
+
+
+### Related Works
+Welcome to follow our other interesting works:
+- [LAM](https://github.com/aigc3d/LAM)
+- [LHM](https://github.com/aigc3d/LHM)
+
+
+### Citation
+```
+@inproceedings{he2025LAM,
+ title={LAM: Large Avatar Model for One-shot Animatable Gaussian Head},
+ author={
+ Yisheng He and Xiaodong Gu and Xiaodan Ye and Chao Xu and Zhengyi Zhao and Yuan Dong and Weihao Yuan and Zilong Dong and Liefeng Bo
+ },
+ booktitle={arXiv preprint arXiv:2502.17796},
+ year={2025}
+}
+```
diff --git a/audio2exp-service/LAM_Audio2Expression/app_lam_audio2exp.py b/audio2exp-service/LAM_Audio2Expression/app_lam_audio2exp.py
new file mode 100644
index 0000000..56c2339
--- /dev/null
+++ b/audio2exp-service/LAM_Audio2Expression/app_lam_audio2exp.py
@@ -0,0 +1,313 @@
+"""
+Copyright 2024-2025 The Alibaba 3DAIGC Team Authors. All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+"""
+import os
+import base64
+
+import gradio as gr
+import argparse
+from omegaconf import OmegaConf
+from gradio_gaussian_render import gaussian_render
+
+from engines.defaults import (
+ default_argument_parser,
+ default_config_parser,
+ default_setup,
+)
+from engines.infer import INFER
+from pathlib import Path
+
+try:
+ import spaces
+except:
+ pass
+
+import patoolib
+
+h5_rendering = True
+
+
+def assert_input_image(input_image,input_zip_textbox):
+ if(os.path.exists(input_zip_textbox)):
+ return
+ if input_image is None:
+ raise gr.Error('No image selected or uploaded!')
+
+
+def prepare_working_dir():
+ import tempfile
+ working_dir = tempfile.TemporaryDirectory()
+ return working_dir
+
+def get_image_base64(path):
+ with open(path, 'rb') as image_file:
+ encoded_string = base64.b64encode(image_file.read()).decode()
+ return f'data:image/png;base64,{encoded_string}'
+
+
+def do_render():
+ print('WebGL rendering ....')
+ return
+
+def audio_loading():
+ print("Audio loading ....")
+ return "None"
+
+def parse_configs():
+ parser = argparse.ArgumentParser()
+ parser.add_argument("--config", type=str)
+ parser.add_argument("--infer", type=str)
+ args, unknown = parser.parse_known_args()
+
+ cfg = OmegaConf.create()
+ cli_cfg = OmegaConf.from_cli(unknown)
+
+ # parse from ENV
+ if os.environ.get("APP_INFER") is not None:
+ args.infer = os.environ.get("APP_INFER")
+ if os.environ.get("APP_MODEL_NAME") is not None:
+ cli_cfg.model_name = os.environ.get("APP_MODEL_NAME")
+
+ args.config = args.infer if args.config is None else args.config
+
+ if args.config is not None:
+ cfg_train = OmegaConf.load(args.config)
+ cfg.source_size = cfg_train.dataset.source_image_res
+ try:
+ cfg.src_head_size = cfg_train.dataset.src_head_size
+ except:
+ cfg.src_head_size = 112
+ cfg.render_size = cfg_train.dataset.render_image.high
+ _relative_path = os.path.join(
+ cfg_train.experiment.parent,
+ cfg_train.experiment.child,
+ os.path.basename(cli_cfg.model_name).split("_")[-1],
+ )
+
+ cfg.save_tmp_dump = os.path.join("exps", "save_tmp", _relative_path)
+ cfg.image_dump = os.path.join("exps", "images", _relative_path)
+ cfg.video_dump = os.path.join("exps", "videos", _relative_path) # output path
+
+ if args.infer is not None:
+ cfg_infer = OmegaConf.load(args.infer)
+ cfg.merge_with(cfg_infer)
+ cfg.setdefault(
+ "save_tmp_dump", os.path.join("exps", cli_cfg.model_name, "save_tmp")
+ )
+ cfg.setdefault("image_dump", os.path.join("exps", cli_cfg.model_name, "images"))
+ cfg.setdefault(
+ "video_dump", os.path.join("dumps", cli_cfg.model_name, "videos")
+ )
+ cfg.setdefault("mesh_dump", os.path.join("dumps", cli_cfg.model_name, "meshes"))
+
+ cfg.motion_video_read_fps = 30
+ cfg.merge_with(cli_cfg)
+
+ cfg.setdefault("logger", "INFO")
+
+ assert cfg.model_name is not None, "model_name is required"
+
+ return cfg, cfg_train
+
+
+def create_zip_archive(output_zip='assets/arkitWithBSData.zip', base_dir=""):
+ if os.path.exists(output_zip):
+ os.remove(output_zip)
+ print(f"Remove previous file: {output_zip}")
+
+ try:
+ # 创建压缩包
+ patoolib.create_archive(
+ archive=output_zip,
+ filenames=[base_dir], # 要压缩的目录
+ verbosity=-1, # 静默模式
+ program='zip' # 指定使用zip格式
+ )
+ print(f"Archive created successfully: {output_zip}")
+ except Exception as e:
+ raise ValueError(f"Archive creation failed: {str(e)}")
+
+
+def demo_lam_audio2exp(infer, cfg):
+ def core_fn(image_path: str, audio_params, working_dir, input_zip_textbox):
+
+ if(os.path.exists(input_zip_textbox)):
+ base_id = os.path.basename(input_zip_textbox).split(".")[0]
+ output_dir = os.path.join('assets', 'sample_lam', base_id)
+ # unzip_dir
+ if (not os.path.exists(os.path.join(output_dir, 'arkitWithBSData'))):
+ run_command = 'unzip -d '+output_dir+' '+input_zip_textbox
+ os.system(run_command)
+ rename_command = 'mv '+os.path.join(output_dir,base_id)+' '+os.path.join(output_dir,'arkitWithBSData')
+ os.system(rename_command)
+ else:
+ base_id = os.path.basename(image_path).split(".")[0]
+
+ # set input audio
+ cfg.audio_input = audio_params
+ cfg.save_json_path = os.path.join("./assets/sample_lam", base_id, 'arkitWithBSData', 'bsData.json')
+ infer.infer()
+
+ output_file_name = base_id+'_'+os.path.basename(audio_params).split(".")[0]+'.zip'
+ assetPrefix = 'gradio_api/file=assets/'
+ output_file_path = os.path.join('./assets',output_file_name)
+
+ create_zip_archive(output_zip=output_file_path, base_dir=os.path.join("./assets/sample_lam", base_id))
+
+ return 'gradio_api/file='+audio_params, assetPrefix+output_file_name
+
+ with gr.Blocks(analytics_enabled=False) as demo:
+ logo_url = './assets/images/logo.jpeg'
+ logo_base64 = get_image_base64(logo_url)
+ gr.HTML(f"""
+
+
+
LAM-A2E: Audio to Expression
+
+
+ """)
+
+ gr.HTML(
+ """ Notes: This project leverages audio input to generate ARKit blendshapes-driven facial expressions in ⚡real-time⚡, powering ultra-realistic 3D avatars generated by LAM.
"""
+ )
+
+ # DISPLAY
+ with gr.Row():
+ with gr.Column(variant='panel', scale=1):
+ with gr.Tabs(elem_id='lam_input_image'):
+ with gr.TabItem('Input Image'):
+ with gr.Row():
+ input_image = gr.Image(label='Input Image',
+ image_mode='RGB',
+ height=480,
+ width=270,
+ sources='upload',
+ type='filepath', # 'numpy',
+ elem_id='content_image',
+ interactive=False)
+ # EXAMPLES
+ with gr.Row():
+ examples = [
+ ['assets/sample_input/barbara.jpg'],
+ ['assets/sample_input/status.png'],
+ ['assets/sample_input/james.png'],
+ ['assets/sample_input/vfhq_case1.png'],
+ ]
+ gr.Examples(
+ examples=examples,
+ inputs=[input_image],
+ examples_per_page=20,
+ )
+
+ with gr.Column():
+ with gr.Tabs(elem_id='lam_input_audio'):
+ with gr.TabItem('Input Audio'):
+ with gr.Row():
+ audio_input = gr.Audio(label='Input Audio',
+ type='filepath',
+ waveform_options={
+ 'sample_rate': 16000,
+ 'waveform_progress_color': '#4682b4'
+ },
+ elem_id='content_audio')
+
+ examples = [
+ ['assets/sample_audio/Nangyanwen_chinese.wav'],
+ ['assets/sample_audio/LiBai_TTS_chinese.wav'],
+ ['assets/sample_audio/LinJing_TTS_chinese.wav'],
+ ['assets/sample_audio/BarackObama_english.wav'],
+ ['assets/sample_audio/HillaryClinton_english.wav'],
+ ['assets/sample_audio/XitongShi_japanese.wav'],
+ ['assets/sample_audio/FangXiao_japanese.wav'],
+ ]
+ gr.Examples(
+ examples=examples,
+ inputs=[audio_input],
+ examples_per_page=10,
+ )
+
+ # SETTING
+ with gr.Row():
+ with gr.Column(variant='panel', scale=1):
+ input_zip_textbox = gr.Textbox(
+ label="Input Local Path to LAM-Generated ZIP File",
+ interactive=True,
+ placeholder="Input Local Path to LAM-Generated ZIP File",
+ visible=True
+ )
+ submit = gr.Button('Generate',
+ elem_id='lam_generate',
+ variant='primary')
+
+ if h5_rendering:
+ gr.set_static_paths(Path.cwd().absolute() / "assets/")
+ with gr.Row():
+ gs = gaussian_render(width=380, height=680)
+
+ working_dir = gr.State()
+ selected_audio = gr.Textbox(visible=False)
+ selected_render_file = gr.Textbox(visible=False)
+
+ submit.click(
+ fn=assert_input_image,
+ inputs=[input_image,input_zip_textbox],
+ queue=False,
+ ).success(
+ fn=prepare_working_dir,
+ outputs=[working_dir],
+ queue=False,
+ ).success(
+ fn=core_fn,
+ inputs=[input_image, audio_input,
+ working_dir, input_zip_textbox],
+ outputs=[selected_audio, selected_render_file],
+ queue=False,
+ ).success(
+ fn=audio_loading,
+ outputs=[selected_audio],
+ js='''(output_component) => window.loadAudio(output_component)'''
+ ).success(
+ fn=do_render(),
+ outputs=[selected_render_file],
+ js='''(selected_render_file) => window.start(selected_render_file)'''
+ )
+
+ demo.queue()
+ demo.launch(inbrowser=True)
+
+
+
+def launch_gradio_app():
+ os.environ.update({
+ 'APP_ENABLED': '1',
+ 'APP_MODEL_NAME':'',
+ 'APP_INFER': 'configs/lam_audio2exp_streaming_config.py',
+ 'APP_TYPE': 'infer.audio2exp',
+ 'NUMBA_THREADING_LAYER': 'omp',
+ })
+
+ args = default_argument_parser().parse_args()
+ args.config_file = 'configs/lam_audio2exp_config_streaming.py'
+ cfg = default_config_parser(args.config_file, args.options)
+ cfg = default_setup(cfg)
+
+ cfg.ex_vol = True
+ infer = INFER.build(dict(type=cfg.infer.type, cfg=cfg))
+
+ demo_lam_audio2exp(infer, cfg)
+
+
+if __name__ == '__main__':
+ launch_gradio_app()
diff --git a/audio2exp-service/LAM_Audio2Expression/assets/images/framework.png b/audio2exp-service/LAM_Audio2Expression/assets/images/framework.png
new file mode 100644
index 0000000000000000000000000000000000000000..210a9757d20e5b3a432bd96c4d88ae9f32c3757d
GIT binary patch
literal 521384
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