知らなきゃ損！Azure Functionsでサードパーティ製ライブラリを超簡単に使いこなす(今回ffmpeg)

# はじめに
こんにちわ！AgVentureLabの藁谷です！
スクラムの実践をしながらJAサイネージというプロダクトを頑張って作っています！

今回、動画ファイルの圧縮処理速度向上のためAzureFunctionsでffmpegを使う必要があり、~~JavaCVじゃめんどくさいから~~ナレッジも多く転がっていることからランタイムのプロセスを利用したコマンド実行を行う方法を試みました。
要はJavaのAzureFunctions上でffmpegをコマンド実行したいって話ですね。
AzureFunctionsで作ればffmpegの並列処理が簡単にできそうなので頑張るぞい！

そこで、次のドキュメントを参考に実装してみたのですが、そのままではうまくいかず試行錯誤の末に解決しました。
同じようなことで悩んでる方の参考になれば幸いです。なおJavaでmavenです。

https://learn.microsoft.com/ja-jp/azure/azure-functions/bring-dependency-to-functions?pivots=programming-language-java

できるだけコピペで再現できるようにまとめたので、参考になったと感じたらいいねください！！！やる気につながります！！！

<!-- 各チャプター -->
<a id="#Chapter1"></a>
# 結論
以下の手順でできます
①AzurePortalにてBASE_PATHの設定
名前：BASE_PATH
値　：/home/site/wwwroot
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953477/4e25daef-1917-9a65-3b8b-548c2060f267.png)

②POMに以下を追加
　・propertiesにstagingDirectoryを追加(任意、２ポチ目で直接ファイルパスを書いてもいい)
　・maven-resources-plugin追加、outputDirectoryにstagingDirectoryを指定
以上です。POMの記載内容は以下の通り。
```xml:pom.xml
~~省略~~
  <properties>
  ~~省略~~
    <stagingDirectory>${project.build.directory}/azure-functions/${functionAppName}</stagingDirectory>
  </properties>
~~省略~~
<build>
<plugins>
  <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-resources-plugin</artifactId>
    <version>3.2.0</version>
    <executions>
      <execution>
        <id>copy-artifacts</id>
        <phase>process-resources</phase>
        <goals>
          <goal>copy-resources</goal>
        </goals>
        <configuration>
          <outputDirectory>${stagingDirectory}</outputDirectory>
          <resources>
            <resource>
              <directory>${project.basedir}</directory>
              <includes>
                <include>artifacts/**</include>
              </includes>
            </resource>
          </resources>
        </configuration>
      </execution>
    </executions>
  </plugin>
~~以下略~~
```
その後AzureFunctionsにデプロイして、Postman等でパラメータ等無しのGETかPOSTしてあげれば、ffmpegのヘルプが表示されるはず。

基本的にPOMの書き方全然わかってなかっただけなんだけど、
努力の推移を見て欲しいから、ここに至った過程もお楽しみください。

----
#### なお重要なのはstagingDirectoryが${project.build.directory}/azure-functions/${functionAppName}ってこと
ちなみに別の公式ドキュにしれっと書いてあるという。ちくしょー！

https://learn.microsoft.com/ja-jp/azure/azure-functions/functions-reference-java?tabs=bash%2Cconsumption

<!-- 各チャプター -->
<a id="#Chapter1.1"></a>
# っていうかGithubにも公開したんで使ってみてね
mainをクローンしたらffmpegの実行ファイルを配置してFunctionsの環境変数を設定してあげればデプロイ・実行可能のはず。
私はVSCodeのAzure拡張機能を使ってデプロイしているので別の方法でできない場合はVSCodeで試してみてください。

https://github.com/AgvUser/ffmpeg-On-Azure-Functions/tree/main

<a id="#Chapter2"></a>
# 困りその1：公式ドキュのPOM、copy-resourcesって何よ
まずはドキュメントの通りディレクトリ構成を整え、プロジェクトルートフォルダにartifacts/ffmpeg/ffmpeg.exeを配置
Function.javaの内容もコピペで持ってくる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953477/f047f19b-8920-7c07-09db-2f0cad05c88d.png)


でも、POMのどこにcopy-resourcesを書けばいいの???????
生成されるtargetファイルに追加できれば良さそうなので色々やってみる。

### 課題A：target直下に任意の資産を配置させる
- 確認１：azure-functions-maven-pluginにそういった機能がある？
`mvn help:describe -Dplugin=<プラグイン名>`
で指定したプラグインのすべてのゴールを一覧表示できるらしい。
そこでazure-functions-maven-pluginがcopy-resourcesのゴールを持つかを確認。
↓ないですね。
```terminal
mvn help:describe -Dplugin=com.microsoft.azure:azure-functions-maven-plugin
~~色々省略 ゴールの一覧のみ表示~~
azure-functions:add
azure-functions:deploy
azure-functions:help
azure-functions:list
azure-functions:package
azure-functions:run
```


- 確認２：maven build時に配置できればOK？
であればcopy-resourcesができれば良いのでmaven-resources-pluginのファイルコピーを使うことにする。
記載箇所と内容は以下のような感じ。
```xml:pom.xml
~~省略~~
<build>
<plugins>
  <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-resources-plugin</artifactId>
    <version>3.2.0</version>
    <executions>
      <execution>
        <id>copy-artifacts</id>
        <phase>process-resources</phase>
        <goals>
          <goal>copy-resources</goal>
        </goals>
        <configuration>
          <outputDirectory>${stagingDirectory}</outputDirectory>
          <resources>
            <resource>
              <directory>${project.basedir}/artifacts/ffmpeg</directory>
              <includes>
                <include>**/*</include>
              </includes>
            </resource>
          </resources>
        </configuration>
      </execution>
    </executions>
  </plugin>
~~以下略~~
```
そんでmvn clean package
`The parameters 'outputDirectory' for goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:copy-resources are missing or invalid`
グエー

<a id="#Chapter3"></a>
# 困りその2：POM、そのままの記載じゃ使えない問題

### 課題B：outputDirectoryのパスが存在しない
確認１：stagingDirectoryをPOMのpropertiesで定義する必要があるのか?
`<outputDirectory>${stagingDirectory}</outputDirectory>`の記載について調べてみたところAzureDevOpsのPipelineで使えるような記事がヒットする。今回はVSCode上で拡張機能を使いデプロイするつもりなので直接POMに定義する必要がありそう。
target直下に配置してみるため${project.build.directory}で定義。
以下の通りPOMのpropertiesに追記した
```xml:pom.xml
~~省略~~
<name>Azure Java Functions</name>

<properties>
　　~~省略 以下一文追加~~
  <stagingDirectory>${project.build.directory}</stagingDirectory>
</properties>

<dependencies>
~~以下略~~
```
渾身のmvn clean package

...
...
...
```
 ------------------------------------------------------------------------
 BUILD SUCCESS
 ------------------------------------------------------------------------
 ```
やった！第三部完！！

いけ！Postman！！
500 Internal Server Error
ふざけんな！パス通ってねぇのか！？

~~そもそもFunctions側にデバッグログも仕込んでないんかというツッコミは受け付けてません~~

<a id="#Chapter4"></a>
# 困りその3：資産の配置先が違う？
とりあえずffmpegが実行されるコマンドを確認してみる。
↓公式ドキュの記載で言うと、これ
`String[] commands = { System.getenv(BASE_PATH) + FFMPEG_PATH, flags };`
とりまデバッグプリント→`/home/site/wwwroot/artifacts/ffmpeg/ffmpeg.exe, -h`
targetファイルがwwwrootに配置されるなら間違ってなさそうだけど…

AzureFunctionsのkudoから資産配置先を見てみる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953477/3663b33e-e825-055e-5aad-dcd442909ef4.png)

ねぇじゃねーか
どうやらtarget/azure-functions/アプリ名　配下に入れれば良い模様
AzureFunctionsは大元の実行ファイルが別にいて、そこから自分が作ったjarを起動させて動かすような仕組みなのかもしれません。
function.jsonもそんな形してそうだし
```json:function.json
{
  "scriptFile" : "../exec-command-sample2-1.0-SNAPSHOT.jar", ←今回作ったjar
  "entryPoint" : "com.function.Function.run",
  "bindings" : [ {
    "type" : "httpTrigger",
    "direction" : "in",
    "name" : "req",
    "methods" : [ "GET", "POST" ],
    "authLevel" : "ANONYMOUS"
  }, {
    "type" : "http",
    "direction" : "out",
    "name" : "$return"
  } ]
}
```
深追いは今回しませんが、target配下に配置したartifactsフォルダがないので、必要な資材だけ指定してデプロイする仕組みなのかも。

そこで、とりあえずHttpExampleフォルダが生成されているtarget/azure-functions/アプリ名　配下にffmpegを配置するようPOMの記載を変更。
先ほど追加したstagingDirectoryのパスを変えてみます。
```xml:pom.xml
  <stagingDirectory>${project.build.directory}/azure-functions/${functionAppName}</stagingDirectory>
```

デプロイしてみると、おっ！良さそう…！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953477/16e39593-6725-28fe-5d5e-9e4640ed7204.png)

Postmanで実行
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953477/10bebae4-dd4f-1632-0a14-9fd9251f2598.png)


やっとできた…

<a id="#Chapter5"></a>
# 言い訳フェイズ
多分もっといいやり方があると思いますが、とりあえず力技で解決できました。
公式ドキュメントちゃんと読んで無いだけだったら申し訳ないのですが、同じ穴にはまっている同士もいるかと思いますのでこのまま公開します。
もっといいやり方あったら教えてください！！！！！

ちなみにチームで並行してFunctionsでのJavaCV実行も調べており、結果的にどちらも実用レベルで使えるようになりました。ナレッジ貯まって嬉しいです！~~私がめんどくさそうと言って避けたJavaCVの調査実装は、開発経験半年もない人がAI使いこなして完了させていておじさんはすごいって思ったんだよ。~~

<a id="#Chapter6"></a>
# ここでハマるかもメモ
・テストコードは消すかFunction.javaの記載に合わせて作り直さないとbuildがこけます。
・こちらはAzure FunctionsのOSがwindows用の記載になっているのでlinuxの場合はffmpegの実行ファイルを変更してください。
・次のようなエラーメッセージが出たらパスの指定が未了か誤っています。
 `Invalid command:[null/artifacts/ffmpeg/ffmpeg.exe, -h]`
 AzurePortalなどからAzureFunctionsの環境変数を以下のように設定してください
 名前：BASE_PATH
 値　：/home/site/wwwroot
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953477/4e25daef-1917-9a65-3b8b-548c2060f267.png)