Azure Functions で FFmpeg を使う

# この記事の目的
Azure Function のアプリで FFmpeg を使えるようにする

# 背景
個人開発しているアプリが音声ファイルを扱うもので、ライブラリが FFmpeg を必要としていた。
Azure Functions Core Tools でのローカル開発時には exe インストールするだけで済んだが、Azure Functions にデプロイするにあたって当然 function にも FFmpeg を仕込まないといけない。そのためどうやって使えるようにするか試行錯誤した。

当初 Docker コンテナーイメージを作成して Azure Functions にデプロイしていたものの (後述)、アプリを改修するたびのビルド&デプロイにものすごく時間がかかり、辟易していた。
後に GitHub Actions での CI/CD の手軽さを知ってしまったので、脱 Docker する方法を模索した。

# 解決方法
1. Azure ストレージのファイル共有に FFmpeg のバイナリファイルを配置する
1. Function Appにストレージのファイル共有をマウントする
1. Function のコードで FFmpeg の PATH を通す

シンプル。

実は [依存関係とサードパーティ製ライブラリを Azure Functions に取り込む | Microsoft Learn](https://learn.microsoft.com/ja-jp/azure/azure-functions/bring-dependency-to-functions?pivots=programming-language-python#bring-dependencies-by-mounting-a-file-share "依存関係とサードパーティ製ライブラリを Azure Functions に取り込む | Microsoft Learn") で紹介されている。（全く気付かなかった）

# 実装

:::note info

以下の説明では Python v2 モデルを使用しています。

:::

### Azure ストレージのファイル共有に FFmpeg のバイナリファイルを配置する
- [FFmpeg 公式](https://www.ffmpeg.org/ "FFmpeg") > [Download] > Linux Static Builds > [32-bit and 64-bit for kernel 3.2.0 and above] からバイナリファイルをダウンロード
    - **ffmpeg-git-amd64-static.tar.xz** については動作確認済み
- 任意の Azure ストレージにファイル共有を作成しておく
    - ストレージアカウントは Premium Files でなく Standard v2 のものでOK
- ダウンロードした tar.xz を解凍し、中のファイルをファイル共有に配置する

ここでは fs01 というファイル共有に ffmpeg ディレクトリを作成し、その中に配置している

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/701198/06aba339-15ec-4335-b55f-e1fbab0b3d43.png)

（ffmpeg ディレクトリ）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/701198/bca609f3-0fd5-4edd-a06c-7584f6d1b5b5.png)


::: note warn
バイナリファイルは **ffmpeg** と **ffprobe** の**両方**を配置する必要があります。
:::

### Function App にストレージのファイル共有をマウントする

Azure CLI でマウントを実行（Windows での例）
- Azure PowerShell にも同様のコマンドは用意されている（[New-AzWebAppAzureStoragePath](https://learn.microsoft.com/ja-jp/powershell/module/az.websites/new-azwebappazurestoragepath?view=azps-13.3.0 "New-AzWebAppAzureStoragePath (Az.Websites) | Microsoft Learn")）

```powershell:
$resourceGroup = "リソースグループ名"
$functionApp = "Function App 名"
$shareId = "任意の文字列 (複数のファイル共有をマウントした時に一意であればOK)"
$share = "ファイル共有名"
$mountPath = "任意のマウントパス"
$AZURE_STORAGE_ACCOUNT = "ストレージアカウント名"
$AZURE_STORAGE_KEY = "ストレージアカウントキー"
```

```powershell:
az webapp config storage-account add `
--resource-group $resourceGroup `
--name $functionApp `
--custom-id $shareId `
--storage-type AzureFiles `
--share-name $share `
--mount-path $mountPath `
--account-name $AZURE_STORAGE_ACCOUNT `
--access-key $AZURE_STORAGE_KEY
```

実行結果は `az webapp config storage-account list` で確認できる

マウントしたファイル共有は Function App のコンテナーから確認できる（= /st01fs01）
- ffmpeg ディレクトリ下に ffmpeg, ffprobe を確認できる

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/701198/81ed0083-eee3-41ff-8a36-6aa5e3ccef67.png)

ファイル共有は Kudu サービス（アプリとは別コンテナー）からも確認できる

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/701198/b67f2f0d-21ba-40eb-b010-52ea405ea788.png)

実はマウント操作は Azure ポータルからも設定できる
（追記 Flex Consumption のものではできない？ App Service plan と通常の Consumption では確認済み）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/701198/15392cac-4eb8-4524-bb8b-caef42730341.png)


### Function のコードで FFmpeg の PATH を通す 
ドキュメントではマウントパスを直接参照する方法は紹介されているものの、やりたいのは PATH を通すこと（= ライブラリに FFmpeg を使わせたい）

マウントパスを Azure Functions の環境変数として設定

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/701198/39f6a560-4b09-4121-8073-e9d84fc966da.png)

PATH にマウントパスを追記する（プログラム内でのみ PATH を通させる）

```python:function_app.py
FFMPEG_PATH = os.getenv("ffmpeg_path")

# 本番環境の Azure Functions でのみ実行
# プログラム内でのみ環境変数を上書きして PATH を通す
if PY_ENV == "production":
    os.environ["PATH"] = os.environ["PATH"] + os.pathsep + FFMPEG_PATH
else:
    pass
```

これで各ライブラリが FFmpeg を使えるようになる。

# 補足（Docker での実装パターン）

上記一切のマウント作業をせずとも、Docker コンテナーイメージでも実行環境を作れる。
ビルド時に `apt-get install -y ffmpeg` させるだけ。
ただし先述の通りビルド&デプロイにものすごい時間がかかることがある。
当然コンテナーなので動作の確実性は高い。安定性を重視するならこちらでも全然アリ。

```dockerfile:Dockerfile
FROM mcr.microsoft.com/azure-functions/python:4-python3.11-appservice

ENV AzureWebJobsScriptRoot=/home/site/wwwroot \
    AzureFunctionsJobHost__Logging__Console__IsEnabled=true

RUN apt-get update && \
    apt-get install -y ffmpeg && \
    rm -rf /var/lib/apt/lists/*

COPY requirements.txt /

RUN python -m pip install --upgrade pip && \
    python -m pip install -r /requirements.txt

COPY . /home/site/wwwroot
```

::: note info
上記の Dockerfile は `func init --docker-only` で生成できるもので、コンテナーイメージだけ SSH 対応版に変更しています。
使用できるイメージは [Microsoft 公式リポジトリ](https://hub.docker.com/r/microsoft/azure-functions-python) で確認できます。
:::

# 参考リンク
#### Microsoft Learn 公式ドキュメント
- [Azure Functions のストレージに関する考慮事項 | Microsoft Learn](https://learn.microsoft.com/ja-jp/azure/azure-functions/storage-considerations?tabs=azure-cli#mount-file-shares "Azure Functions のストレージに関する考慮事項 | Microsoft Learn")
- [ファイル共有を Python Function App にマウントする - Azure CLI | Microsoft Learn](https://learn.microsoft.com/ja-jp/azure/azure-functions/scripts/functions-cli-mount-files-storage-linux "ファイル共有を Python Function App にマウントする - Azure CLI | Microsoft Learn")
- [az webapp config storage-account | Microsoft Learn](https://learn.microsoft.com/ja-jp/cli/azure/webapp/config/storage-account?view=azure-cli-latest "az webapp config storage-account | Microsoft Learn")
- [依存関係とサードパーティ製ライブラリを Azure Functions に取り込む | Microsoft Learn](https://learn.microsoft.com/ja-jp/azure/azure-functions/bring-dependency-to-functions?pivots=programming-language-python#bring-dependencies-by-mounting-a-file-share "依存関係とサードパーティ製ライブラリを Azure Functions に取り込む | Microsoft Learn")

#### FFmpeg 公式
- [FFmpeg](https://www.ffmpeg.org/ "FFmpeg")

# （FFmpeg を使っている個人開発アプリ）
- [LyriXer](https://lyrixer.com/ "LyriXer - Lyrics Extractor AI")
    - 楽曲中の歌詞を自動抽出するアプリ
- [Musigen](https://musigen.xyz/ "Musigen - Image to Music Geneartor")
    - 画像から音楽を自動生成するアプリ