# トラブルシューティング記録
## Hugging Face Spaces デプロイ時のバージョン互換性問題
### 問題
Gradio SDK を使用すると、HF Spaces のベースイメージと Gradio/huggingface_hub のバージョンが競合する。
### 試行錯誤の記録
| 試行 | 内容 | 結果 |
|------|------|------|
| 1 | gradio>=5.0.0 | `HfFolder` インポートエラー(huggingface_hub 互換性問題) |
| 2 | gradio==4.44.1 + huggingface_hub==0.26.2 | `source` パラメータエラー |
| 3 | gradio==5.6.0 + huggingface_hub==0.26.2 | `HfFolder` インポートエラー |
| 4 | gradio==4.44.0 + gradio_client==1.3.0 + huggingface_hub==0.25.2 | `sources` パラメータエラー |
| 5 | gradio==4.31.0 + gradio_client==0.16.4 | 依存関係競合(gradio は 0.16.2 を要求) |
| 6 | gradio==4.31.0(gradio_client 自動解決) | `json_schema_to_python_type` エラー |
| 7 | gradio==4.19.2 | `HfFolder` インポートエラー(ベースイメージの huggingface_hub が新しすぎる) |
### 根本原因
- HF Spaces の Gradio SDK ベースイメージは `huggingface-hub>=0.30` と `pydantic~=1.0` を含む
- これが様々な Gradio バージョンと競合する
- `gradio_client` の `json_schema_to_python_type` 関数に pydantic バージョン依存のバグがある
### 解決策
**Docker SDK を使用する**
Docker SDK を使うことで、ベースイメージの制約から解放され、任意のバージョンを使用可能。
```yaml
# README.md のメタデータ
sdk: docker
```
### 教訓
1. HF Spaces で Gradio SDK を使う場合、ベースイメージのバージョンに注意
2. 複雑な依存関係がある場合は最初から Docker SDK を検討
3. gradio_client は gradio が自動管理するため明示指定しない
4. pydantic v1 と v2 の互換性問題に注意
## Docker ビルド時のパッケージ名問題
### 問題
Debian Trixie (Testing) で `libgl1-mesa-glx` が非推奨になった。
### 解決策
```dockerfile
# 古い(エラー)
libgl1-mesa-glx
libxrender-dev
# 新しい(正しい)
libgl1
libxrender1
```
## huggingface_hub バージョン問題(Docker SDK でも発生)
### 問題
Docker SDK を使っても、pip が最新の huggingface_hub をインストールし、gradio 4.44.1 と互換性がない。
### 原因
- huggingface_hub 0.25+ で `HfFolder` クラスが削除/移動された
- gradio 4.44.1 は `from huggingface_hub import HfFolder` を使用
### 解決策
requirements.txt で huggingface_hub を gradio より先に固定:
```
huggingface_hub==0.24.0
gradio==4.44.1
```
## gradio_client / pydantic バージョン互換性問題
### 問題
`json_schema_to_python_type` で `TypeError: argument of type 'bool' is not iterable` エラー。
### 原因
- gradio_client のコードで `"const" in schema` をチェックする箇所がある
- pydantic v2 では schema が bool を返す場合があり、`in` 演算子が使えない
- gradio_client と pydantic のバージョン不整合
### エラーログ
```
File "gradio_client/utils.py", line 337, in json_schema_to_python_type
if "const" in schema:
TypeError: argument of type 'bool' is not iterable
```
### 解決策
互換性のあるバージョンを固定(gradio_client は明示指定しない):
```
huggingface_hub==0.21.4
gradio==4.19.2
pydantic==2.6.4
```
### 重要ポイント
- gradio_client は gradio が自動管理するため明示指定しない
- pydantic はスキーマ生成に影響するため、互換バージョンを使用
## gradio_client 明示指定による依存関係エラー
### 問題
```
ERROR: Cannot install gradio_client==0.11.0 because:
gradio 4.19.2 depends on gradio-client==0.10.1
```
### 原因
- gradio_client==0.11.0 を明示指定したが、gradio 4.19.2 は gradio-client==0.10.1 を要求
- 既にTROUBLESHOOTING.md の教訓3に「明示指定しない」と記載されていたが無視した
### 解決策
gradio_client の明示指定を削除し、pip に自動解決させる
### 教訓(再確認)
**gradio_client は絶対に明示指定しない。gradio が依存関係として自動管理する。**
## Gradio 4.19.2 で streaming が動作しない
### 問題
- カメラ映像は表示されるが、OCR/VLM処理が実行されない
- UIの位置情報・検出情報が更新されない
### 原因
- `webcam.stream()` メソッドが Gradio 4.19.2 で期待通り動作しない
- `streaming=True` の Image コンポーネントでは `.stream()` より `.change()` が確実
### 解決策
```python
# 修正前(動作しない)
webcam.stream(
fn=process_webcam,
inputs=[webcam],
outputs=[webcam, location_output, info_output],
)
# 修正後
webcam.change(
fn=process_webcam,
inputs=[webcam],
outputs=[webcam, location_output, info_output],
)
```
### デバッグ方法
処理関数にログを追加して Container タブで確認:
```python
print(f"[DEBUG] process_webcam called, frame type: {type(frame)}")
```
## PaddleOCR 3.x で Unknown argument エラー
### 問題
```
ValueError: Unknown argument: show_log
```
### 原因
- PaddleOCR 3.x で API が変更され、`show_log`, `enable_mkldnn`, `cpu_threads` などの引数が廃止された
### 解決策
```python
# 修正前(PaddleOCR 2.x)
self._ocr = PaddleOCR(
use_angle_cls=True,
lang=self.lang,
use_gpu=self.use_gpu,
show_log=False,
enable_mkldnn=True,
cpu_threads=2,
)
# 修正後(PaddleOCR 3.x)
self._ocr = PaddleOCR(
use_angle_cls=True,
lang=self.lang,
use_gpu=self.use_gpu,
)
```
## Gradio でデフォルト外カメラ(背面カメラ)を使用
### 問題
- スマホでアクセス時、デフォルトで内カメラ(前面)が選択される
- 位置特定には外カメラ(背面)が必要
### 原因
- `gr.Image(sources=["webcam"])` は内カメラをデフォルトにする
- Gradio には直接外カメラを指定するオプションがない
### 解決策
JavaScript で `facingMode: 'environment'` を指定:
```python
camera_js = """
() => {
const setRearCamera = () => {
const video = document.querySelector('video');
if (video && video.srcObject) {
navigator.mediaDevices.getUserMedia({
video: { facingMode: { exact: 'environment' } }
}).then(stream => {
video.srcObject = stream;
}).catch(err => {
console.log('外カメラ切り替え失敗:', err);
});
}
};
setTimeout(setRearCamera, 1000);
}
"""
with gr.Blocks(js=camera_js) as demo:
...
```
## Gradio 4.19.2 で streaming=True + .change() が動作しない
### 問題
- `streaming=True` で `.change()` を使っても、サーバーにフレームが送信されない
- Container ログにデバッグ出力が表示されない
### 原因
- Gradio 4.19.2 の `gr.Image` で `streaming=True` を使っても、
実際にはサーバーへの自動送信が行われない場合がある
- ブラウザ側でフレームがキャプチャされるだけで、サーバーに送信されない
### 解決策
手動キャプチャ方式に変更:
```python
# streaming=False に変更
webcam = gr.Image(
sources=["webcam"],
streaming=False, # 手動キャプチャ
label="カメラ映像",
)
# 解析ボタンを追加
analyze_btn = gr.Button("📸 解析する", variant="primary")
# ボタンクリックで処理
analyze_btn.click(
fn=process_webcam,
inputs=[webcam],
outputs=[webcam, location_output, info_output],
)
```
### カメラ切り替えボタンの実装
```python
switch_camera_js = """
async () => {
const video = document.querySelector('video');
const tracks = video.srcObject.getVideoTracks();
const currentFacing = tracks[0].getSettings().facingMode || 'user';
const newFacing = currentFacing === 'user' ? 'environment' : 'user';
tracks.forEach(track => track.stop());
const newStream = await navigator.mediaDevices.getUserMedia({
video: { facingMode: { exact: newFacing } }
});
video.srcObject = newStream;
}
"""
switch_camera_btn.click(fn=None, js=switch_camera_js)
```
## gr.Image streaming=False で画像がサーバーに送信されない
### 問題
- 解析ボタンを押しても「カメラを起動してください」表示
- Container ログにデバッグ出力なし
- `frame=None` がサーバーに渡されている
### 原因
- `gr.Image(sources=["webcam"], streaming=False)` では、カメラUI内の**撮影ボタン**を押して静止画をキャプチャしないと画像がセットされない
- カメラプレビューは表示されるが、ユーザーが明示的に撮影しないと `None` のまま
### 解決策
UIの説明を改善して、ユーザーに操作手順を明示:
```
1. カメラUI内の撮影ボタンで写真を撮る
2. 解析ボタンを押す
```
### ラベルの改善
```python
webcam = gr.Image(
sources=["webcam"],
streaming=False,
label="📷 カメラで撮影してから解析ボタンを押してください",
)
```
## デフォルト外カメラが適用されない
### 問題
- `gr.Blocks(js=camera_js)` を設定したが、カメラが内カメラのまま
### 原因
- ページ読み込み時にカメラがまだ起動していない
- タイミングの問題で `video.srcObject` が `null`
### 解決策
リトライロジック付きのJavaScriptを使用:
```javascript
const initRearCamera = async () => {
await new Promise(r => setTimeout(r, 2000));
const video = document.querySelector('video');
if (!video || !video.srcObject) {
setTimeout(initRearCamera, 1000); // リトライ
return;
}
// 外カメラに切り替え
const stream = await navigator.mediaDevices.getUserMedia({
video: { facingMode: { ideal: 'environment' } }
});
video.srcObject = stream;
};
initRearCamera();
```
## PaddleOCR 3.x で use_gpu が廃止
### 問題
```
ValueError: Unknown argument: use_gpu
```
### 原因
- PaddleOCR 3.x で `use_gpu` 引数も廃止された
- `show_log`, `enable_mkldnn`, `cpu_threads` に続いて `use_gpu` も削除
### 解決策
```python
# 修正前
self._ocr = PaddleOCR(
use_angle_cls=True,
lang=self.lang,
use_gpu=self.use_gpu,
)
# 修正後(PaddleOCR 3.x)
self._ocr = PaddleOCR(
use_angle_cls=True,
lang=self.lang,
)
```
### 教訓
PaddleOCR 3.x では以下の引数のみ使用:
- `use_angle_cls`
- `lang`
## カメラ切り替えボタンをオーバーレイ表示
### 問題
- カメラ切り替えボタンが撮影画面の外にあり使いづらい
### 解決策
JavaScriptでボタンを動的に追加:
```javascript
const btn = document.createElement('button');
btn.className = 'camera-switch-overlay';
btn.innerHTML = '🔄 内/外';
btn.style.cssText = 'position:absolute;top:10px;right:10px;z-index:1000;...';
imageContainer.appendChild(btn);
```
MutationObserverでDOMの変更を監視し、ボタンが消えたら再追加。
## Gradio gr.Image の制限事項
### 問題
- `streaming=True` でも解析が開始されない
- 画面中央タップでカメラが停止する
- デフォルトで内カメラになる
- 撮影モードへの切り替えが必要
### 原因
- Gradio の `gr.Image(sources=["webcam"])` は制御が難しい
- ブラウザ標準のカメラUIを使用しており、カスタマイズ不可
### 解決策
**カスタムHTML + JavaScript でカメラUIを自前実装**
```html
```
```javascript
// 外カメラをデフォルトで起動
navigator.mediaDevices.getUserMedia({
video: { facingMode: { ideal: 'environment' } }
});
// 2秒間隔で自動解析
setInterval(() => {
// canvasにキャプチャ
ctx.drawImage(video, 0, 0);
// Base64でサーバーに送信
const imageData = canvas.toDataURL('image/jpeg', 0.8);
// Gradioの隠しinputに設定して送信
}, 2000);
```
### メリット
- 画面タップでカメラが停止しない
- デフォルト外カメラ
- 常時撮影モード
- カメラ切り替えボタンをオーバーレイ表示
## カスタムHTML + JavaScript の Failed to fetch エラー
### 問題
```
Error: Failed to fetch
```
Containerログに上記のみ表示され、サーバー処理が実行されない
### 原因
- カスタムHTMLからGradioの隠しinputに値を設定してもイベントが正しく発火しない
- GradioのセキュリティでAPIへの直接アクセスが制限される可能性
### 解決策
カスタムHTMLを諦め、Gradio標準の `gr.Image(streaming=True)` + `webcam.stream()` を使用:
```python
webcam = gr.Image(
sources=["webcam"],
streaming=True,
type="numpy",
)
webcam.stream(
fn=process_image,
inputs=[webcam],
outputs=[location_out, info_out],
time_limit=30,
stream_every=2, # 2秒間隔
)
```
### 補足
- カメラ切り替えは別ボタン + JavaScriptで対応
## Gradio 4.19.2 で stream() の引数エラー
### 問題
```
TypeError: event_trigger() got an unexpected keyword argument 'time_limit'
```
### 原因
- `time_limit` と `stream_every` は Gradio 4.44+ で追加された引数
- Gradio 4.19.2 では使用不可
### 解決策
```python
# 修正前(Gradio 4.44+)
webcam.stream(
fn=process_image,
inputs=[webcam],
outputs=[location_out, info_out],
time_limit=30,
stream_every=2,
)
# 修正後(Gradio 4.19.2 互換)
webcam.stream(
fn=process_image,
inputs=[webcam],
outputs=[location_out, info_out],
)
```
### 教訓
Gradio のバージョンによってAPIが大きく異なるため、使用バージョンを必ず確認すること。
## Gradio 4.19.2 で常時ストリーミング
### 問題
- `gr.Blocks` + `webcam.stream()` では `time_limit`, `stream_every` が使えない
- カメラ画面をクリックしないと撮影が始まらない
### 解決策
`gr.Interface` + `live=True` を使用:
```python
demo = gr.Interface(
fn=process_and_annotate,
inputs=gr.Image(sources=["webcam"], streaming=True),
outputs=gr.Image(),
live=True, # これが重要!
)
```
参考: https://www.gradio.app/guides/reactive-interfaces
## 最初から外カメラを強制
### 問題
- デフォルトで内カメラが起動し、その後外カメラに切り替わる
### 原因
- `facingMode: { ideal: 'environment' }` は「できれば外カメラ」という意味
- ブラウザがまず内カメラを起動してしまう
### 解決策
`{ exact: 'environment' }` を使用して強制:
```javascript
const stream = await navigator.mediaDevices.getUserMedia({
video: {
facingMode: { exact: 'environment' }
}
});
```
参考: https://stackoverflow.com/questions/52812091/getusermedia-selecting-rear-camera-on-mobile
### 注意
- `exact` は外カメラがない場合エラーになる
- フォールバックとして `ideal` も用意しておく
## Gradio 5.0+ へのアップグレード
### 問題
- Gradio 4.19.2 では `gr.WebcamOptions` がサポートされていない
- JavaScript で外カメラを強制しても、1クリックが必要
- `live=True` でも映像上にオーバーレイが表示されない
### 原因
- `gr.WebcamOptions` は Gradio 5.24+ で追加された機能
- Gradio 4.x の `gr.Image` は制御が限定的
- iOS ブラウザはセキュリティ上、ユーザークリックなしでカメラ自動起動不可(これは回避不可)
### 解決策
Gradio 5.0+ にアップグレード:
```python
# requirements.txt
gradio>=5.0.0
numpy>=1.24.0,<2.0.0 # PaddleOCR互換性のため
# app.py
webcam = gr.Image(
sources=["webcam"],
streaming=True,
webcam_options=gr.WebcamOptions(
mirror=False,
constraints={"facingMode": {"exact": "environment"}}
)
)
webcam.stream(
fn=process_and_annotate,
inputs=webcam,
outputs=output,
time_limit=300,
stream_every=0.5
)
```
### 変更点
1. `gradio>=5.0.0` に変更
2. `huggingface_hub` と `pydantic` の固定削除(gradio に任せる)
3. `numpy<2.0.0` 制限追加(PaddleOCR 互換性)
4. `gr.WebcamOptions` で外カメラ強制
5. `webcam.stream()` で `time_limit`, `stream_every` が使用可能
### 参考資料
- https://www.gradio.app/docs/gradio/video
- https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints/facingMode
- https://pypi.org/project/gradio/
## gradio-webrtc への移行
### 問題
- Gradio標準の `gr.Image` では録画ボタンを押す必要がある
- 入力と出力が別々のコンポーネントに分かれる
- カメラ画面内にオーバーレイが表示できない
### 解決策
gradio-webrtc を使用して単一コンポーネントでストリーミング:
```python
from gradio_webrtc import WebRTC
with gr.Blocks() as demo:
webrtc = WebRTC(
label="カメラ",
mode="send-receive", # 入出力両方
modality="video",
)
webrtc.stream(
fn=process_frame,
inputs=[webrtc],
outputs=[webrtc], # 同じコンポーネントに出力
time_limit=600,
)
```
### メリット
1. **単一コンポーネント** - 入力と出力が同じ場所に表示
2. **自動ストリーミング** - ボタン不要で常時処理
3. **低遅延** - WebRTCによるリアルタイム通信
4. **オーバーレイ可能** - 処理結果を映像上に直接描画
### 外カメラ強制
JavaScript で getUserMedia をオーバーライド:
```javascript
const originalGetUserMedia = navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices);
navigator.mediaDevices.getUserMedia = async (constraints) => {
if (constraints && constraints.video) {
constraints.video = {
...constraints.video,
facingMode: { ideal: 'environment' }
};
}
return originalGetUserMedia(constraints);
};
```
### 参考資料
- https://pypi.org/project/gradio-webrtc/
- https://www.gradio.app/guides/object-detection-from-webcam-with-webrtc
## FastRTC への移行
### 問題
- `gradio-webrtc` が deprecated になり、`fastrtc` への移行が必要
- `get_hf_turn_credentials` が deprecated
### 解決策
```python
# requirements.txt
fastrtc
# app.py
from fastrtc import WebRTC, get_cloudflare_turn_credentials
rtc_config = get_cloudflare_turn_credentials if os.getenv("HF_TOKEN") else None
webrtc = WebRTC(
label="Camera",
mode="send-receive",
modality="video",
track_constraints=TRACK_CONSTRAINTS,
rtc_configuration=rtc_config,
)
```
### 参考資料
- https://fastrtc.org/
- https://fastrtc.org/reference/credentials/
## Gradio 6.0 Deprecation Warnings
### 問題
```
DeprecationWarning: The 'css' parameter in the Blocks constructor will be removed in Gradio 6.0
DeprecationWarning: The 'js' parameter in the Blocks constructor will be removed in Gradio 6.0
DeprecationWarning: The 'show_api' parameter in launch() will be removed in Gradio 6.0
```
### 解決策
```python
# 修正前
with gr.Blocks(title="dokoCame", css=custom_css, js=auto_start_js) as demo:
...
demo.launch(show_api=False)
# 修正後
with gr.Blocks(title="dokoCame") as demo:
...
demo.launch(css=CUSTOM_CSS, js=AUTO_START_JS)
```
`show_api=False` は現状のバージョンでは代替がないため削除。
## WebRTC 映像の反転問題
### 問題
- 外カメラ使用時に映像が左右反転している
### 原因
- WebRTC/FastRTC がデフォルトでミラーリングを適用している
### 解決策
`cv2.flip()` で反転を修正:
```python
def process_video_frame(frame: np.ndarray) -> np.ndarray:
frame = cv2.flip(frame, 1) # 水平反転(左右のみ)
# ...
```
- `cv2.flip(frame, 0)` - 垂直反転(上下)
- `cv2.flip(frame, 1)` - 水平反転(左右)
- `cv2.flip(frame, -1)` - 両方反転(180度回転)
### ICE Connection 失敗
### 問題
```
Received ICE candidate for unknown connection: xxxxx
```
画面が真っ暗になり接続失敗。
### 原因
- クラウド環境では TURN サーバーが必要
- NAT/ファイアウォールを超えるため
### 解決策
HF_TOKEN を設定して Cloudflare TURN サーバーを使用:
```python
from fastrtc import get_cloudflare_turn_credentials
rtc_config = get_cloudflare_turn_credentials if os.getenv("HF_TOKEN") else None
```
HF Spaces の Settings → Repository secrets に `HF_TOKEN` を追加。
### 参考資料
- https://huggingface.co/blog/fastrtc-cloudflare
## PaddleOCR モデルの事前ダウンロード
### 問題
- 毎回起動時にモデルをダウンロードして時間がかかる
### 解決策
Dockerfile でビルド時に事前ダウンロード:
```dockerfile
RUN python -c "from paddleocr import PaddleOCR; PaddleOCR(use_angle_cls=True, lang='japan')" || true
```
## 再発防止チェックリスト
### 必須確認事項(コード変更時に必ずチェック)
1. **外カメラ設定**
- `track_constraints` に `"facingMode": {"exact": "environment"}` が含まれているか
- `track_constraints` の構造が正しいか(`{"video": {...}}` 形式)
2. **フッター非表示**
- `CUSTOM_CSS` に `footer { display: none !important; }` が含まれているか
- `gr.Blocks()` に `css=CUSTOM_CSS` が渡されているか
3. **ボタンラベル**
- `button_labels` に日本語ではなく英語("Start", "Stop")が設定されているか
- "録音" という文字列がコード内に存在しないか
4. **Stream.ui.launch() は使わない**
- `Stream.ui.launch()` はデフォルトUIを使うため、カスタマイズ不可
- 必ず `gr.Blocks()` + `WebRTC` コンポーネントを使用すること
### track_constraints の正しい形式
```python
# 正しい形式
TRACK_CONSTRAINTS = {
"video": {
"width": {"ideal": 1280},
"height": {"ideal": 720},
"frameRate": {"ideal": 15},
"facingMode": {"exact": "environment"},
}
}
# 間違った形式(videoキーがない)
TRACK_CONSTRAINTS = {
"width": {"ideal": 1280},
"facingMode": {"exact": "environment"},
}
```